forked from ScoDoc/ScoDoc
Compare commits
5 Commits
188534819b
...
3b2888cd5b
Author | SHA1 | Date | |
---|---|---|---|
|
3b2888cd5b | ||
|
b542e7dab5 | ||
|
f7a8c1d2db | ||
5cefe1a337 | |||
c0d2f66081 |
@ -64,6 +64,11 @@ def assiduite(assiduite_id: int = None):
|
|||||||
"est_just": False or True,
|
"est_just": False or True,
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
SAMPLES
|
||||||
|
-------
|
||||||
|
/assiduite/1;
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
return get_model_api_object(Assiduite, assiduite_id, Identite)
|
return get_model_api_object(Assiduite, assiduite_id, Identite)
|
||||||
@ -93,6 +98,11 @@ def assiduite_justificatifs(assiduite_id: int = None, long: bool = False):
|
|||||||
...
|
...
|
||||||
]
|
]
|
||||||
```
|
```
|
||||||
|
SAMPLES
|
||||||
|
-------
|
||||||
|
/assiduite/1/justificatifs;
|
||||||
|
/assiduite/1/justificatifs/long;
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
return get_assiduites_justif(assiduite_id, long)
|
return get_assiduites_justif(assiduite_id, long)
|
||||||
@ -156,6 +166,13 @@ def assiduites_count(
|
|||||||
metric: la/les métriques de comptage (journee, demi, heure, compte)
|
metric: la/les métriques de comptage (journee, demi, heure, compte)
|
||||||
split: divise le comptage par état
|
split: divise le comptage par état
|
||||||
|
|
||||||
|
SAMPLES
|
||||||
|
-------
|
||||||
|
/assiduites/1/count;
|
||||||
|
/assiduites/1/count/query?etat=retard;
|
||||||
|
/assiduites/1/count/query?split;
|
||||||
|
/assiduites/1/count/query?etat=present,retard&metric=compte,heure;
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Récupération de l'étudiant
|
# Récupération de l'étudiant
|
||||||
@ -221,6 +238,7 @@ def assiduites(etudid: int = None, nip=None, ine=None, with_query: bool = False)
|
|||||||
date_fin:<string:date_fin_iso>
|
date_fin:<string:date_fin_iso>
|
||||||
etat:<array[string]:etat>
|
etat:<array[string]:etat>
|
||||||
formsemestre_id:<int:formsemestre_id>
|
formsemestre_id:<int:formsemestre_id>
|
||||||
|
with_justifs:<bool:with_justifs>
|
||||||
|
|
||||||
PARAMS
|
PARAMS
|
||||||
-----
|
-----
|
||||||
@ -231,6 +249,14 @@ def assiduites(etudid: int = None, nip=None, ine=None, with_query: bool = False)
|
|||||||
date_fin:date de fin de l'assiduité (inférieur ou égal)
|
date_fin:date de fin de l'assiduité (inférieur ou égal)
|
||||||
etat:etat de l'étudiant → absent, present ou retard
|
etat:etat de l'étudiant → absent, present ou retard
|
||||||
formsemestre_id:l'identifiant du formsemestre concerné par l'assiduité
|
formsemestre_id:l'identifiant du formsemestre concerné par l'assiduité
|
||||||
|
with_justif:ajoute les justificatifs liés à l'assiduité
|
||||||
|
|
||||||
|
SAMPLES
|
||||||
|
-------
|
||||||
|
/assiduites/1;
|
||||||
|
/assiduites/1/query?etat=retard;
|
||||||
|
/assiduites/1/query?moduleimpl_id=1;
|
||||||
|
/assiduites/1/query?with_justifs=;
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@ -300,6 +326,11 @@ def assiduites_evaluations(etudid: int = None, nip=None, ine=None):
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
|
SAMPLES
|
||||||
|
-------
|
||||||
|
/assiduites/1/evaluations;
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
"""
|
"""
|
||||||
@ -344,7 +375,7 @@ def evaluation_assiduites(evaluation_id):
|
|||||||
|
|
||||||
CATEGORY
|
CATEGORY
|
||||||
--------
|
--------
|
||||||
evaluations
|
Évaluations
|
||||||
"""
|
"""
|
||||||
# Récupération de l'évaluation
|
# Récupération de l'évaluation
|
||||||
try:
|
try:
|
||||||
@ -384,6 +415,7 @@ def assiduites_group(with_query: bool = False):
|
|||||||
etat:<array[string]:etat>
|
etat:<array[string]:etat>
|
||||||
etudids:<array[int]:etudids>
|
etudids:<array[int]:etudids>
|
||||||
formsemestre_id:<int:formsemestre_id>
|
formsemestre_id:<int:formsemestre_id>
|
||||||
|
with_justif:<bool:with_justif>
|
||||||
|
|
||||||
PARAMS
|
PARAMS
|
||||||
-----
|
-----
|
||||||
@ -395,6 +427,11 @@ def assiduites_group(with_query: bool = False):
|
|||||||
etat:etat de l'étudiant → absent, present ou retard
|
etat:etat de l'étudiant → absent, present ou retard
|
||||||
etudids:liste des ids des étudiants concernés par la recherche
|
etudids:liste des ids des étudiants concernés par la recherche
|
||||||
formsemestre_id:l'identifiant du formsemestre concerné par l'assiduité
|
formsemestre_id:l'identifiant du formsemestre concerné par l'assiduité
|
||||||
|
with_justifs:ajoute les justificatifs liés à l'assiduité
|
||||||
|
|
||||||
|
SAMPLES
|
||||||
|
-------
|
||||||
|
/assiduites/group/query?etudids=1,2,3;
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@ -474,6 +511,13 @@ def assiduites_formsemestre(formsemestre_id: int, with_query: bool = False):
|
|||||||
date_fin:date de fin de l'assiduité (inférieur ou égal)
|
date_fin:date de fin de l'assiduité (inférieur ou égal)
|
||||||
etat:etat de l'étudiant → absent, present ou retard
|
etat:etat de l'étudiant → absent, present ou retard
|
||||||
formsemestre_id:l'identifiant du formsemestre concerné par l'assiduité
|
formsemestre_id:l'identifiant du formsemestre concerné par l'assiduité
|
||||||
|
|
||||||
|
SAMPLES
|
||||||
|
-------
|
||||||
|
/assiduites/formsemestre/1;
|
||||||
|
/assiduites/formsemestre/1/query?etat=retard;
|
||||||
|
/assiduites/formsemestre/1/query?moduleimpl_id=1;
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Récupération du formsemestre à partir du formsemestre_id
|
# Récupération du formsemestre à partir du formsemestre_id
|
||||||
@ -549,6 +593,13 @@ def assiduites_formsemestre_count(
|
|||||||
formsemestre_id:l'identifiant du formsemestre concerné par l'assiduité
|
formsemestre_id:l'identifiant du formsemestre concerné par l'assiduité
|
||||||
metric: la/les métriques de comptage (journee, demi, heure, compte)
|
metric: la/les métriques de comptage (journee, demi, heure, compte)
|
||||||
split: divise le comptage par état
|
split: divise le comptage par état
|
||||||
|
|
||||||
|
SAMPLES
|
||||||
|
-------
|
||||||
|
/assiduites/formsemestre/1/count;
|
||||||
|
/assiduites/formsemestre/1/count/query?etat=retard;
|
||||||
|
/assiduites/formsemestre/1/count/query?etat=present,retard&metric=compte,heure;
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Récupération du formsemestre à partir du formsemestre_id
|
# Récupération du formsemestre à partir du formsemestre_id
|
||||||
@ -621,6 +672,11 @@ def assiduite_create(etudid: int = None, nip=None, ine=None):
|
|||||||
]
|
]
|
||||||
```
|
```
|
||||||
|
|
||||||
|
SAMPLES
|
||||||
|
-------
|
||||||
|
/assiduite/1/create;[{""date_debut"": ""2023-10-27T08:00"",""date_fin"": ""2023-10-27T10:00"",""etat"": ""absent""}]
|
||||||
|
/assiduite/1/create;[{""date_debut"": ""2023-10-27T08:00"",""date_fin"": ""2023-10-27T10:00"",""etat"": ""absent""}]
|
||||||
|
|
||||||
"""
|
"""
|
||||||
# Récupération de l'étudiant
|
# Récupération de l'étudiant
|
||||||
etud: Identite = tools.get_etud(etudid, nip, ine)
|
etud: Identite = tools.get_etud(etudid, nip, ine)
|
||||||
@ -696,6 +752,11 @@ def assiduites_create():
|
|||||||
]
|
]
|
||||||
```
|
```
|
||||||
|
|
||||||
|
SAMPLES
|
||||||
|
-------
|
||||||
|
/assiduites/create;[{""etudid"":1,""date_debut"": ""2023-10-26T08:00"",""date_fin"": ""2023-10-26T10:00"",""etat"": ""absent""}]
|
||||||
|
/assiduites/create;[{""etudid"":-1,""date_debut"": ""2023-10-26T08:00"",""date_fin"": ""2023-10-26T10:00"",""etat"": ""absent""}]
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
create_list: list[object] = request.get_json(force=True)
|
create_list: list[object] = request.get_json(force=True)
|
||||||
@ -874,6 +935,10 @@ def assiduite_delete():
|
|||||||
]
|
]
|
||||||
```
|
```
|
||||||
|
|
||||||
|
SAMPLES
|
||||||
|
-------
|
||||||
|
/assiduite/delete;[2,2,3]
|
||||||
|
|
||||||
"""
|
"""
|
||||||
# Récupération des ids envoyés dans la liste
|
# Récupération des ids envoyés dans la liste
|
||||||
assiduites_list: list[int] = request.get_json(force=True)
|
assiduites_list: list[int] = request.get_json(force=True)
|
||||||
@ -958,6 +1023,13 @@ def assiduite_edit(assiduite_id: int):
|
|||||||
"est_just"?: bool
|
"est_just"?: bool
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
SAMPLES
|
||||||
|
-------
|
||||||
|
/assiduite/1/edit;{""etat"":""absent""}
|
||||||
|
/assiduite/1/edit;{""moduleimpl_id"":2}
|
||||||
|
/assiduite/1/edit;{""etat"": ""retard"",""moduleimpl_id"":3}
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Récupération de l'assiduité à modifier
|
# Récupération de l'assiduité à modifier
|
||||||
@ -1013,6 +1085,12 @@ def assiduites_edit():
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
```
|
```
|
||||||
|
SAMPLES
|
||||||
|
-------
|
||||||
|
/assiduites/edit;[{""etat"":""absent"",""assiduite_id"":1}]
|
||||||
|
/assiduites/edit;[{""moduleimpl_id"":2,""assiduite_id"":1}]
|
||||||
|
/assiduites/edit;[{""etat"": ""retard"",""moduleimpl_id"":3,""assiduite_id"":1}]
|
||||||
|
|
||||||
"""
|
"""
|
||||||
edit_list: list[object] = request.get_json(force=True)
|
edit_list: list[object] = request.get_json(force=True)
|
||||||
|
|
||||||
|
@ -38,9 +38,11 @@ from app.scodoc.sco_groups import get_group_members
|
|||||||
@scodoc
|
@scodoc
|
||||||
@permission_required(Permission.ScoView)
|
@permission_required(Permission.ScoView)
|
||||||
def justificatif(justif_id: int = None):
|
def justificatif(justif_id: int = None):
|
||||||
"""Retourne un objet justificatif à partir de son id
|
"""Retourne un objet justificatif à partir de son id.
|
||||||
|
|
||||||
Exemple de résultat:
|
Exemple de résultat:
|
||||||
|
|
||||||
|
```json
|
||||||
{
|
{
|
||||||
"justif_id": 1,
|
"justif_id": 1,
|
||||||
"etudid": 2,
|
"etudid": 2,
|
||||||
@ -52,6 +54,11 @@ def justificatif(justif_id: int = None):
|
|||||||
"entry_date": "2022-10-31T08:00+01:00",
|
"entry_date": "2022-10-31T08:00+01:00",
|
||||||
"user_id": 1 or null,
|
"user_id": 1 or null,
|
||||||
}
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
SAMPLES
|
||||||
|
-------
|
||||||
|
/justificatif/1;
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@ -112,6 +119,12 @@ def justificatifs(etudid: int = None, nip=None, ine=None, with_query: bool = Fal
|
|||||||
order:retourne les justificatifs dans l'ordre décroissant (non vide = True)
|
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)
|
courant:retourne les justificatifs de l'année courante (bool : v/t ou f)
|
||||||
group_id:<int:group_id>
|
group_id:<int:group_id>
|
||||||
|
|
||||||
|
SAMPLES
|
||||||
|
-------
|
||||||
|
/justificatifs/1;
|
||||||
|
/justificatifs/1/query?etat=attente;
|
||||||
|
|
||||||
"""
|
"""
|
||||||
# Récupération de l'étudiant
|
# Récupération de l'étudiant
|
||||||
etud: Identite = tools.get_etud(etudid, nip, ine)
|
etud: Identite = tools.get_etud(etudid, nip, ine)
|
||||||
@ -174,6 +187,11 @@ def justificatifs_dept(dept_id: int = None, with_query: bool = False):
|
|||||||
order:retourne les justificatifs dans l'ordre décroissant (non vide = True)
|
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)
|
courant:retourne les justificatifs de l'année courante (bool : v/t ou f)
|
||||||
group_id:<int:group_id>
|
group_id:<int:group_id>
|
||||||
|
|
||||||
|
SAMPLES
|
||||||
|
-------
|
||||||
|
/justificatifs/dept/1;
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Récupération du département et des étudiants du département
|
# Récupération du département et des étudiants du département
|
||||||
@ -267,6 +285,11 @@ def justificatifs_formsemestre(formsemestre_id: int, with_query: bool = False):
|
|||||||
order:retourne les justificatifs dans l'ordre décroissant (non vide = True)
|
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)
|
courant:retourne les justificatifs de l'année courante (bool : v/t ou f)
|
||||||
group_id:<int:group_id>
|
group_id:<int:group_id>
|
||||||
|
|
||||||
|
SAMPLES
|
||||||
|
-------
|
||||||
|
/justificatifs/formsemestre/1;
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Récupération du formsemestre
|
# Récupération du formsemestre
|
||||||
@ -334,6 +357,9 @@ def justif_create(etudid: int = None, nip=None, ine=None):
|
|||||||
...
|
...
|
||||||
]
|
]
|
||||||
```
|
```
|
||||||
|
SAMPLES
|
||||||
|
-------
|
||||||
|
/justificatif/1/create;[{""date_debut"": ""2023-10-27T08:00"",""date_fin"": ""2023-10-27T10:00"",""etat"": ""attente""}]
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@ -475,6 +501,12 @@ def justif_edit(justif_id: int):
|
|||||||
"date_fin"?: str
|
"date_fin"?: str
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
SAMPLES
|
||||||
|
-------
|
||||||
|
/justificatif/1/edit;{""etat"":""valide""}
|
||||||
|
/justificatif/1/edit;{""raison"":""MEDIC""}
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Récupération du justificatif à modifier
|
# Récupération du justificatif à modifier
|
||||||
@ -589,6 +621,11 @@ def justif_delete():
|
|||||||
...
|
...
|
||||||
]
|
]
|
||||||
```
|
```
|
||||||
|
|
||||||
|
SAMPLES
|
||||||
|
-------
|
||||||
|
/justificatif/delete;[2, 2, 3]
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Récupération des justif_ids
|
# Récupération des justif_ids
|
||||||
@ -834,6 +871,11 @@ def justif_remove(justif_id: int = None):
|
|||||||
def justif_list(justif_id: int = None):
|
def justif_list(justif_id: int = None):
|
||||||
"""
|
"""
|
||||||
Liste les fichiers du justificatif
|
Liste les fichiers du justificatif
|
||||||
|
|
||||||
|
SAMPLES
|
||||||
|
-------
|
||||||
|
/justificatif/1/list;
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Récupération du justificatif concerné
|
# Récupération du justificatif concerné
|
||||||
@ -876,6 +918,11 @@ def justif_list(justif_id: int = None):
|
|||||||
def justif_justifies(justif_id: int = None):
|
def justif_justifies(justif_id: int = None):
|
||||||
"""
|
"""
|
||||||
Liste assiduite_id justifiées par le justificatif
|
Liste assiduite_id justifiées par le justificatif
|
||||||
|
|
||||||
|
SAMPLES
|
||||||
|
-------
|
||||||
|
/justificatif/1/justifies;
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# On récupère le justificatif concerné
|
# On récupère le justificatif concerné
|
||||||
|
280
app/templates/doc/ScoDoc9API.j2
Normal file
280
app/templates/doc/ScoDoc9API.j2
Normal file
@ -0,0 +1,280 @@
|
|||||||
|
{# Documentation de l'API ScoDoc 9 #}
|
||||||
|
# API pour ScoDoc 9
|
||||||
|
|
||||||
|
!!! warning "Attention"
|
||||||
|
*Page générée par la commande `flask gen-api-doc`. Ne pas modifier manuellement.*
|
||||||
|
|
||||||
|
|
||||||
|
L'API ScoDoc permet à des applications tierces d'interroger ScoDoc. Elle offre
|
||||||
|
un accès aux objets de l'application via une API REST.
|
||||||
|
|
||||||
|
Les composants internes de ScoDoc, et notamment le schéma de la base de données,
|
||||||
|
sont susceptibles d'évoluer à tout moment sans préavis: il est vivement
|
||||||
|
déconseillé d'écrire une extension ne passant pas par l'API. Vous ne devez même
|
||||||
|
pas supposer qu'il existe une base de données SQL.
|
||||||
|
|
||||||
|
La version ScoDoc 9 a introduit une nouvelle API avec un nouveau mécanisme d'authentification.
|
||||||
|
**Les clients de l'ancienne API ScoDoc 7 doivent être adaptés pour fonctionner avec ScoDoc 9.**
|
||||||
|
|
||||||
|
Cette API est encore incomplète: n'hésitez pas à demander de nouveaux accès ([contacts](Contact.md))
|
||||||
|
(et canal `#API` du Discord développeurs si vous y avez accès).
|
||||||
|
|
||||||
|
L'API fournit des données JSON, sauf exception (bulletins PDF par exemple).
|
||||||
|
|
||||||
|
Les objets ScoDoc manipulables sont identifiés par des id numériques.
|
||||||
|
|
||||||
|
* `etudid` : étudiant
|
||||||
|
* `formation_id` : un programme de formation (page "programmes");
|
||||||
|
* `ue_id` : une UE dans un programme;
|
||||||
|
* `matiere_id` : une matière dans un programme;
|
||||||
|
* `module_id` : un module dans un programme;
|
||||||
|
* `moduleimpl_id` : un module réalisé dans un semestre;
|
||||||
|
* `formsemestre_id` : un "semestre" de formation.
|
||||||
|
|
||||||
|
(pour plus de précisions, voir le [guide développeurs](GuideDeveloppeurs.md))
|
||||||
|
|
||||||
|
L'URL complète est de la forme:
|
||||||
|
`https://scodoc.example.com/ScoDoc/api/<fonction>`.
|
||||||
|
(<fonction> à choisir dans [Référence](#reference))
|
||||||
|
|
||||||
|
## Configuration de ScoDoc pour utiliser l'API
|
||||||
|
|
||||||
|
Il est nécessaire de disposer d'un compte utilisateur avec les droits adéquats.
|
||||||
|
|
||||||
|
Les droits à accorder dépendent des fonctionnalités nécessaires. la permission
|
||||||
|
`ScoView` est généralement suffisante car elle permet toutes les consultations.
|
||||||
|
Cependant si, par l'API, on veut effectuer des opérations de modification ou
|
||||||
|
encore consulter les comptes utilisateurs, d'autres droits (`ScoChangeGroups`,
|
||||||
|
`UsersView`, `ScoSuperAdmin`, ...) peuvent être requis. La consultation du
|
||||||
|
[tableau récapitulatif](#tableau-recapitulatif-des-entrees-de-lapi) ou la ligne
|
||||||
|
`permission`de chaque entrée vous donnera la permission requise pour chaque
|
||||||
|
opération.
|
||||||
|
|
||||||
|
En général, il est recommandé de créer un rôle, de lui attribuer les permissions
|
||||||
|
que l'on veut utiliser, puis de créer un utilisateur ayant ce rôle.
|
||||||
|
|
||||||
|
En ligne de commande, cela peut se faire comme suit (voir détail des commandes
|
||||||
|
[sur le guide de configuration](GuideConfig.md)).
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# se connecter comme utilisateur scodoc
|
||||||
|
su - scodoc
|
||||||
|
|
||||||
|
# Créer un rôle
|
||||||
|
flask create-role LecteurAPI
|
||||||
|
# Lui donner les droits nécessaires: ici ScoView
|
||||||
|
flask edit-role LecteurAPI -a ScoView
|
||||||
|
|
||||||
|
# Créer un nouvel utilisateur avec ce rôle:
|
||||||
|
flask user-create lecteur_api LecteurAPI @all
|
||||||
|
|
||||||
|
# Ou bien, si on veut utiliser un compte existant:
|
||||||
|
# associer notre rôle à un utilisateur
|
||||||
|
flask user-role lecteur_api -a LecteurAPI
|
||||||
|
|
||||||
|
|
||||||
|
# Au besoin, changer le mot de passe de l'utilisateur
|
||||||
|
# (on aura besoin de ce mot de passe dans la configuration du client d'API)
|
||||||
|
flask user-password lecteur_api
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
Si vous êtes intéressé par le développement, voir
|
||||||
|
|
||||||
|
* [la section sur les tests unitaires de l'API](TestsScoDoc.md#tests-de-lapi-scodoc9);
|
||||||
|
* [la documentation développeurs](GuideDeveloppeurs.md) et sur les [vues de l'API](DevInternals.md#vues-de-lapi-et-permissions).
|
||||||
|
|
||||||
|
!!! note
|
||||||
|
|
||||||
|
* Si vous utilisez le CAS, pensez à laisser les comptes utilisateurs API se
|
||||||
|
connecter via ScoDoc sans CAS. Pour cela, cocher l'option
|
||||||
|
*Autorise connexion via CAS si CAS est activé*
|
||||||
|
dans leur formulaire de configuration.
|
||||||
|
|
||||||
|
* Si l'utilisateur est associé à un département (cas des comptes créés via l'interface Web),
|
||||||
|
il ne pourra accéder à l'API que via une *route départementale*, c'est à dire une route comprenant
|
||||||
|
l'acronyme de son département, de la forme `https://...//ScoDoc/DEPARTEMENT/api/...`.
|
||||||
|
|
||||||
|
## Essais avec HTTPie
|
||||||
|
|
||||||
|
[HTTPie](https://httpie.io/) est un client universel livre et gratuit très commode, disponible
|
||||||
|
pour Windows, Linux, en ligne de commande ou interface graphique.
|
||||||
|
|
||||||
|
Exemple d'utilisation en ligne de commande et interroger votre ScoDoc pour
|
||||||
|
obtenir la liste des départements:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
http -a USER:PASSWORD POST 'http://localhost:5000/ScoDoc/api/tokens'
|
||||||
|
```
|
||||||
|
|
||||||
|
Qui affiche:
|
||||||
|
|
||||||
|
```text
|
||||||
|
HTTP/1.1 200 OK
|
||||||
|
Content-Length: 50
|
||||||
|
Content-Type: application/json
|
||||||
|
Date: Thu, 05 May 2022 04:29:33 GMT
|
||||||
|
|
||||||
|
{
|
||||||
|
"token": "jS7iVl1234cRDzboAfO5xseE0Ain6Zyz"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
(remplacer `USER:PASSWORD` par les identifiants de votre utilisateur et adapter
|
||||||
|
l'URL qui est ici celle d'un client local sur le serveur de test).
|
||||||
|
|
||||||
|
Avec ce jeton (*token*), on peut interroger le serveur:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
http GET http://localhost:5000/ScoDoc/api/departements "Authorization:Bearer jS7iVlH1234cRDzboAfO5xseE0Ain6Zyz"
|
||||||
|
```
|
||||||
|
|
||||||
|
qui affiche par exemple:
|
||||||
|
|
||||||
|
```text
|
||||||
|
HTTP/1.1 200 OK
|
||||||
|
Content-Length: 151
|
||||||
|
Content-Type: application/json
|
||||||
|
Date: Thu, 05 May 2022 05:21:33 GMT
|
||||||
|
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"acronym": "TAPI",
|
||||||
|
"date_creation": "Wed, 04 May 2022 21:09:25 GMT",
|
||||||
|
"description": null,
|
||||||
|
"id": 1,
|
||||||
|
"visible": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
## Fonctions d'API ScoDoc 9
|
||||||
|
|
||||||
|
La documentation ci-dessous concerne la nouvelle API, disponible à partir de la
|
||||||
|
version de ScoDoc 9.3.25.
|
||||||
|
|
||||||
|
### Accès à l'API REST
|
||||||
|
|
||||||
|
L'API est accessible à l'adresse:
|
||||||
|
`https://scodoc.monsite.tld/ScoDoc/api/<fonction>`, et aussi via les *routes
|
||||||
|
départementales* de la forme
|
||||||
|
`https://scodoc.monsite.tld/ScoDoc/<dept_acronyme>/api/<fonction>` pour un accès
|
||||||
|
avec des droits restreints au département indiqué. La liste des `<fonctions>` est
|
||||||
|
donnée ci-dessous dans [Référence](#reference).
|
||||||
|
|
||||||
|
#### Authentification
|
||||||
|
|
||||||
|
Lors de votre authentification (*connexion avec login et mot de passe*) à Scodoc, il
|
||||||
|
vous sera attribué un jeton (token jwt *généré automatiquement*) vous permettant
|
||||||
|
d'utiliser l'api suivant les droits correspondant à votre session.
|
||||||
|
|
||||||
|
Pour obtenir le jeton, il faut un compte sur ScoDoc (`user_name`et `password`).
|
||||||
|
Les autorisations et rôles sont gérés exactement comme pour l'application.
|
||||||
|
|
||||||
|
Exemple avec `curl` (un outil en ligne de commande présent sur la plupart des
|
||||||
|
systèmes, voir plus haut pour la même chose avec la commande `http`):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -u user_name:password --request POST https://SERVEUR/ScoDoc/api/tokens
|
||||||
|
```
|
||||||
|
|
||||||
|
où `SERVEUR` est l'adresse (IP ou nom) de votre serveur.
|
||||||
|
La réponse doit ressembler à ceci:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"token": "LuXXxk+i74TXYZZl8MulgbiCGmVHXXX"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Vous trouverez dans `/opt/scodoc/tests/api/exemple-api-basic.py` un exemple
|
||||||
|
complet en python d'interrogation de l'API.
|
||||||
|
|
||||||
|
#### Codes HTTP
|
||||||
|
|
||||||
|
Chaque appel à l'API donne lieu à une réponse retournant un code spécifique en
|
||||||
|
fonction du résultat obtenu. L'analyse de ce code vous permet de vous assurer
|
||||||
|
que la requête a été traitée avec succès.
|
||||||
|
|
||||||
|
Tous les codes >= 400 indiquent que la requête n'a pas été traitée avec succès
|
||||||
|
par le serveur ScoDoc.
|
||||||
|
|
||||||
|
* [200](https://developer.mozilla.org/fr/docs/Web/HTTP/Status/200) : OK.
|
||||||
|
* [401](https://developer.mozilla.org/fr/docs/Web/HTTP/Status/401) : Authentification nécessaire. (jeton non précisé ou invalide)
|
||||||
|
* [403](https://developer.mozilla.org/fr/docs/Web/HTTP/Status/403) : Action
|
||||||
|
non autorisée pour l'utilisateur associé au jeton.
|
||||||
|
* [404](https://developer.mozilla.org/fr/docs/Web/HTTP/Status/401) : Adresse
|
||||||
|
incorrecte, paramètre manquant ou invalide, ou objet inexistant.
|
||||||
|
* [500](https://developer.mozilla.org/fr/docs/Web/HTTP/Status/500) : Erreur
|
||||||
|
inconnue côté serveur.
|
||||||
|
|
||||||
|
## Règles générales
|
||||||
|
|
||||||
|
* une route s'écrit comme une suite de noms et d'identifiants;
|
||||||
|
* les noms token, département, formation, formsemestre, groupe, etudiant,
|
||||||
|
bulletin, absence, logo, programme, évaluation, résultat, décision désignent
|
||||||
|
des types d'objets;
|
||||||
|
* les noms (verbes ou groupes verbaux): set_etudiant, remove_etudiant, query,
|
||||||
|
create, delete, edit, order sont des actions;
|
||||||
|
* les noms restants (ids, courants, long, ...) sont des options, les autres noms
|
||||||
|
sont des options ou des actions;
|
||||||
|
* le dernier nom apparaissant sur une route donne le type d'objet renvoyé. Ce
|
||||||
|
nom peut apparaître au singulier ou au pluriel.
|
||||||
|
* au singulier un seul objet est renvoyé, si aucun objet n'est trouvé, retourne un 404;
|
||||||
|
* au pluriel une collection d'objets est renvoyée, si aucun objet n'est
|
||||||
|
trouvé, retourne une collection vide.
|
||||||
|
* un type d'objet au singulier est généralement suivi immédiatement de son
|
||||||
|
identifiant (unique). Exception: pour un étudiant, on peut également utiliser
|
||||||
|
le NIP ou l'INE (qui ne sont pas uniques dans la base car un étudiant de même
|
||||||
|
INE/NIP peut passer par plusieurs départements).
|
||||||
|
|
||||||
|
## Référence
|
||||||
|
|
||||||
|
La [carte syntaxique](#carte-syntaxique) vous permet de retrouver une entrée à
|
||||||
|
partir de sa syntaxe (le `?` amène sur la documentation associée).
|
||||||
|
|
||||||
|
Le [tableau récapitulatif](#tableau-recapitulatif-des-entrees-de-lapi) vous
|
||||||
|
permet de rechercher une entrée à partir du résultat attendu.
|
||||||
|
|
||||||
|
### Carte syntaxique
|
||||||
|
|
||||||
|
<div style="overflow: scroll;">
|
||||||
|
<div style="width: 1200px;">
|
||||||
|
![carte_syntaxique](img/API_Chart.svg)
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
(carte générée avec `flask gen-api-doc`)
|
||||||
|
|
||||||
|
### Tableau récapitulatif des entrées de l'API
|
||||||
|
|
||||||
|
{{table_api|safe}}
|
||||||
|
|
||||||
|
(table générée avec `flask gen-api-doc`)
|
||||||
|
|
||||||
|
#### Note sur les exemples d'utilisation
|
||||||
|
|
||||||
|
Pour uniformiser les résultats des exemples, ceux sont soumis à quelques post-traitements non réalisés par l'API.
|
||||||
|
|
||||||
|
- les clés sont triées (ce n'est pas toujours garanti);
|
||||||
|
- les listes de plus de 2 éléments sont tronquées à 2 éléments, la fin de la liste étant
|
||||||
|
représentée par la notation en json '...';
|
||||||
|
- les dates (au format ISO) sont systématiquement remplacées par une date fixe et ne sont pas réalistes.
|
||||||
|
|
||||||
|
{{doc_api|safe}}
|
||||||
|
|
||||||
|
|
||||||
|
---------------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
### En savoir plus
|
||||||
|
|
||||||
|
Voir exemples d'utilisation de l'API en Python, dans `tests/api/`.
|
||||||
|
|
||||||
|
|
||||||
|
!!! note "Voir aussi"
|
||||||
|
|
||||||
|
- [Guide configuration et ligne de commande](GuideConfig.md)
|
||||||
|
- [Guide administrateur ScoDoc](GuideAdminSys.md)
|
||||||
|
- [ServicesXml](ServicesXml.md) : anciens web services XML (obsolète)
|
||||||
|
- [FAQ](FAQ.md)
|
||||||
|
- [Contacts](Contact.md)
|
13
scodoc.py
13
scodoc.py
@ -740,19 +740,6 @@ def generate_ens_calendars(): # generate-ens-calendars
|
|||||||
edt_ens.generate_ens_calendars()
|
edt_ens.generate_ens_calendars()
|
||||||
|
|
||||||
|
|
||||||
@app.cli.command()
|
|
||||||
@click.option(
|
|
||||||
"-e",
|
|
||||||
"--endpoint",
|
|
||||||
default="api",
|
|
||||||
help="Endpoint à partir duquel générer la carte des routes",
|
|
||||||
)
|
|
||||||
@with_appcontext
|
|
||||||
def gen_api_map(endpoint): # gen-api-map
|
|
||||||
"""Génère la carte des routes de l'API."""
|
|
||||||
tools.gen_api_map(app, endpoint_start=endpoint)
|
|
||||||
|
|
||||||
|
|
||||||
@app.cli.command()
|
@app.cli.command()
|
||||||
@click.option(
|
@click.option(
|
||||||
"-e",
|
"-e",
|
||||||
|
@ -1,36 +1,38 @@
|
|||||||
"entry_name";"url";"permission";"method";"content"
|
"entry_name";"url";"permission";"method";"content"
|
||||||
"assiduite";"/assiduite/1";"ScoView";"GET";
|
"assiduite";"/assiduite/1";"ScoView";"GET";
|
||||||
"assiduites";"/assiduites/1";"ScoView";"GET";
|
"assiduite_justificatifs";"/assiduite/1/justificatifs";"ScoView";"GET";
|
||||||
"assiduites";"/assiduites/1/query?etat=retard";"ScoView";"GET";
|
"assiduite_justificatifs";"/assiduite/1/justificatifs/long";"ScoView";"GET";
|
||||||
"assiduites";"/assiduites/1/query?moduleimpl_id=1";"ScoView";"GET";
|
|
||||||
"assiduites";"/assiduites/1/query?with_justifs=";"ScoView";"GET";
|
|
||||||
"assiduites_count";"/assiduites/1/count";"ScoView";"GET";
|
"assiduites_count";"/assiduites/1/count";"ScoView";"GET";
|
||||||
"assiduites_count";"/assiduites/1/count/query?etat=retard";"ScoView";"GET";
|
"assiduites_count";"/assiduites/1/count/query?etat=retard";"ScoView";"GET";
|
||||||
"assiduites_count";"/assiduites/1/count/query?split";"ScoView";"GET";
|
"assiduites_count";"/assiduites/1/count/query?split";"ScoView";"GET";
|
||||||
"assiduites_count";"/assiduites/1/count/query?etat=present,retard&metric=compte,heure";"ScoView";"GET";
|
"assiduites_count";"/assiduites/1/count/query?etat=present,retard&metric=compte,heure";"ScoView";"GET";
|
||||||
|
"assiduites";"/assiduites/1";"ScoView";"GET";
|
||||||
|
"assiduites";"/assiduites/1/query?etat=retard";"ScoView";"GET";
|
||||||
|
"assiduites";"/assiduites/1/query?moduleimpl_id=1";"ScoView";"GET";
|
||||||
|
"assiduites";"/assiduites/1/query?with_justifs=";"ScoView";"GET";
|
||||||
|
"assiduites_evaluations";"/assiduites/1/evaluations";"ScoView";"GET";
|
||||||
|
"assiduites_group";"/assiduites/group/query?etudids=1,2,3";"ScoView";"GET";
|
||||||
"assiduites_formsemestre";"/assiduites/formsemestre/1";"ScoView";"GET";
|
"assiduites_formsemestre";"/assiduites/formsemestre/1";"ScoView";"GET";
|
||||||
"assiduites_formsemestre";"/assiduites/formsemestre/1/query?etat=retard";"ScoView";"GET";
|
"assiduites_formsemestre";"/assiduites/formsemestre/1/query?etat=retard";"ScoView";"GET";
|
||||||
"assiduites_formsemestre";"/assiduites/formsemestre/1/query?moduleimpl_id=1";"ScoView";"GET";
|
"assiduites_formsemestre";"/assiduites/formsemestre/1/query?moduleimpl_id=1";"ScoView";"GET";
|
||||||
"assiduites_formsemestre_count";"/assiduites/formsemestre/1/count";"ScoView";"GET";
|
"assiduites_formsemestre_count";"/assiduites/formsemestre/1/count";"ScoView";"GET";
|
||||||
"assiduites_formsemestre_count";"/assiduites/formsemestre/1/count/query?etat=retard";"ScoView";"GET";
|
"assiduites_formsemestre_count";"/assiduites/formsemestre/1/count/query?etat=retard";"ScoView";"GET";
|
||||||
"assiduites_formsemestre_count";"/assiduites/formsemestre/1/count/query?etat=present,retard&metric=compte,heure";"ScoView";"GET";
|
"assiduites_formsemestre_count";"/assiduites/formsemestre/1/count/query?etat=present,retard&metric=compte,heure";"ScoView";"GET";
|
||||||
"assiduites_group";"/assiduites/group/query?etudids=1,2,3";"ScoView";"GET";
|
|
||||||
"assiduites_justificatifs";"/assiduite/1/justificatifs";"ScoView";"GET";
|
|
||||||
"assiduites_justificatifs";"/assiduite/1/justificatifs/long";"ScoView";"GET";
|
|
||||||
"assiduite_create";"/assiduite/1/create";"UsersAdmin";"POST";"[{""date_debut"": ""2023-10-27T08:00"",""date_fin"": ""2023-10-27T10:00"",""etat"": ""absent""}]"
|
"assiduite_create";"/assiduite/1/create";"UsersAdmin";"POST";"[{""date_debut"": ""2023-10-27T08:00"",""date_fin"": ""2023-10-27T10:00"",""etat"": ""absent""}]"
|
||||||
"assiduite_create";"/assiduite/1/create";"UsersAdmin";"POST";"[{""date_debut"": ""2023-10-27T08:00"",""date_fin"": ""2023-10-27T10:00"",""etat"": ""absent""}]"
|
"assiduite_create";"/assiduite/1/create";"UsersAdmin";"POST";"[{""date_debut"": ""2023-10-27T08:00"",""date_fin"": ""2023-10-27T10:00"",""etat"": ""absent""}]"
|
||||||
"assiduites_create";"/assiduites/create";"UsersAdmin";"POST";"[{""etudid"":1,""date_debut"": ""2023-10-26T08:00"",""date_fin"": ""2023-10-26T10:00"",""etat"": ""absent""}]"
|
"assiduites_create";"/assiduites/create";"UsersAdmin";"POST";"[{""etudid"":1,""date_debut"": ""2023-10-26T08:00"",""date_fin"": ""2023-10-26T10:00"",""etat"": ""absent""}]"
|
||||||
"assiduites_create";"/assiduites/create";"UsersAdmin";"POST";"[{""etudid"":-1,""date_debut"": ""2023-10-26T08:00"",""date_fin"": ""2023-10-26T10:00"",""etat"": ""absent""}]"
|
"assiduites_create";"/assiduites/create";"UsersAdmin";"POST";"[{""etudid"":-1,""date_debut"": ""2023-10-26T08:00"",""date_fin"": ""2023-10-26T10:00"",""etat"": ""absent""}]"
|
||||||
|
"assiduite_delete";"/assiduite/delete";"UsersAdmin";"POST";"[2,2,3]"
|
||||||
"assiduite_edit";"/assiduite/1/edit";"UsersAdmin";"POST";"{""etat"":""absent""}"
|
"assiduite_edit";"/assiduite/1/edit";"UsersAdmin";"POST";"{""etat"":""absent""}"
|
||||||
"assiduite_edit";"/assiduite/1/edit";"UsersAdmin";"POST";"{""moduleimpl_id"":2}"
|
"assiduite_edit";"/assiduite/1/edit";"UsersAdmin";"POST";"{""moduleimpl_id"":2}"
|
||||||
"assiduite_edit";"/assiduite/1/edit";"UsersAdmin";"POST";"{""etat"": ""retard"",""moduleimpl_id"":3}"
|
"assiduite_edit";"/assiduite/1/edit";"UsersAdmin";"POST";"{""etat"": ""retard"",""moduleimpl_id"":3}"
|
||||||
"assiduite_delete";"/assiduite/delete";"UsersAdmin";"POST";"[2,2,3]"
|
|
||||||
"justificatif";"/justificatif/1";"ScoView";"GET";
|
"justificatif";"/justificatif/1";"ScoView";"GET";
|
||||||
"justificatifs";"/justificatifs/1";"ScoView";"GET";
|
"justificatifs";"/justificatifs/1";"ScoView";"GET";
|
||||||
"justificatifs";"/justificatifs/1/query?etat=attente";"ScoView";"GET";
|
"justificatifs";"/justificatifs/1/query?etat=attente";"ScoView";"GET";
|
||||||
"justificatifs_dept";"/justificatifs/dept/1";"ScoView";"GET";
|
"justificatifs_dept";"/justificatifs/dept/1";"ScoView";"GET";
|
||||||
"justificatifs_formsemestre";"/justificatifs/formsemestre/1";"ScoView";"GET";
|
"justificatifs_formsemestre";"/justificatifs/formsemestre/1";"ScoView";"GET";
|
||||||
"justificatif_create";"/justificatif/1/create";"UsersAdmin";"POST";"[{""date_debut"": ""2023-10-27T08:00"",""date_fin"": ""2023-10-27T10:00"",""etat"": ""attente""}]"
|
"justif_edit";"/justificatif/1/edit";"UsersAdmin";"POST";"{""etat"":""valide""}"
|
||||||
"justificatif_edit";"/justificatif/1/edit";"UsersAdmin";"POST";"{""etat"":""valide""}"
|
"justif_edit";"/justificatif/1/edit";"UsersAdmin";"POST";"{""raison"":""MEDIC""}"
|
||||||
"justificatif_edit";"/justificatif/1/edit";"UsersAdmin";"POST";"{""raison"":""MEDIC""}"
|
"justif_delete";"/justificatif/delete";"UsersAdmin";"POST";"[2, 2, 3]"
|
||||||
"justificatif_delete";"/justificatif/delete";"UsersAdmin";"POST";"[2,2,3]"
|
"justif_list";"/justificatif/1/list";"ScoView";"GET";
|
||||||
|
"justif_justifies";"/justificatif/1/justifies";"UsersAdmin";"GET";
|
|
@ -4,12 +4,14 @@ Script permettant de générer une carte SVG de l'API de ScoDoc
|
|||||||
Écrit par Matthias HARTMANN
|
Écrit par Matthias HARTMANN
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import sys
|
|
||||||
import xml.etree.ElementTree as ET
|
|
||||||
import re
|
import re
|
||||||
|
import sys
|
||||||
|
import unicodedata
|
||||||
|
import xml.etree.ElementTree as ET
|
||||||
|
|
||||||
|
from flask import render_template
|
||||||
|
|
||||||
from app.auth.models import Permission
|
from app.auth.models import Permission
|
||||||
from flask import render_template
|
|
||||||
|
|
||||||
|
|
||||||
class COLORS:
|
class COLORS:
|
||||||
@ -136,7 +138,7 @@ class Token:
|
|||||||
element, x_offset, y_offset
|
element, x_offset, y_offset
|
||||||
)
|
)
|
||||||
# Préparation du lien vers la doc de la route
|
# Préparation du lien vers la doc de la route
|
||||||
href = "#" + self.func_name.replace("_", "-")
|
href = "#" + self.func_name
|
||||||
if self.query and not href.endswith("-query"):
|
if self.query and not href.endswith("-query"):
|
||||||
href += "-query"
|
href += "-query"
|
||||||
question_mark_group = _create_question_mark_group(current_end_coords, href)
|
question_mark_group = _create_question_mark_group(current_end_coords, href)
|
||||||
@ -270,6 +272,13 @@ class Token:
|
|||||||
return group
|
return group
|
||||||
|
|
||||||
|
|
||||||
|
def strip_accents(s):
|
||||||
|
"""Retourne la chaîne s séparant les accents et les caractères de base."""
|
||||||
|
return "".join(
|
||||||
|
c for c in unicodedata.normalize("NFD", s) if unicodedata.category(c) != "Mn"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def _create_svg_element(text, color="rgb(230,156,190)"):
|
def _create_svg_element(text, color="rgb(230,156,190)"):
|
||||||
"""
|
"""
|
||||||
Fonction générale pour créer un élément SVG simple
|
Fonction générale pour créer un élément SVG simple
|
||||||
@ -503,9 +512,7 @@ def analyze_api_routes(app, endpoint_start: str) -> tuple:
|
|||||||
child.method = method
|
child.method = method
|
||||||
current_token.add_child(child)
|
current_token.add_child(child)
|
||||||
|
|
||||||
# Gestion de doctable
|
href = func_name
|
||||||
doctable = parse_doctable_doc(func.__doc__ or "")
|
|
||||||
href = func_name.replace("_", "-")
|
|
||||||
if child.query and not href.endswith("-query"):
|
if child.query and not href.endswith("-query"):
|
||||||
href += "-query"
|
href += "-query"
|
||||||
|
|
||||||
@ -527,14 +534,15 @@ def analyze_api_routes(app, endpoint_start: str) -> tuple:
|
|||||||
|
|
||||||
if func_name not in doctable_lines:
|
if func_name not in doctable_lines:
|
||||||
doctable_lines[func_name] = {
|
doctable_lines[func_name] = {
|
||||||
"doctable": doctable,
|
|
||||||
"method": method,
|
"method": method,
|
||||||
"nom": func_name,
|
"nom": func_name,
|
||||||
"href": href,
|
"href": href,
|
||||||
|
"query": doc_dict.get("QUERY", "") != "",
|
||||||
"permission": permissions,
|
"permission": permissions,
|
||||||
"description": doc_dict.get("", ""),
|
"description": doc_dict.get("", ""),
|
||||||
"params": doc_dict.get("PARAMS", ""),
|
"params": doc_dict.get("PARAMS", ""),
|
||||||
"category": doc_dict.get("CATEGORY", [False])[0] or category,
|
"category": doc_dict.get("CATEGORY", [False])[0] or category,
|
||||||
|
"samples": doc_dict.get("SAMPLES"),
|
||||||
}
|
}
|
||||||
|
|
||||||
# On met à jour le token courant pour le prochain segment
|
# On met à jour le token courant pour le prochain segment
|
||||||
@ -547,15 +555,13 @@ def analyze_api_routes(app, endpoint_start: str) -> tuple:
|
|||||||
|
|
||||||
|
|
||||||
# point d'entrée de la commande `flask gen-api-map`
|
# point d'entrée de la commande `flask gen-api-map`
|
||||||
def gen_api_map(app, endpoint_start="api.") -> str:
|
def gen_api_map(api_map: Token, doctable_lines: dict[str, dict]) -> str:
|
||||||
"""
|
"""
|
||||||
Fonction permettant de générer une carte SVG de l'API de ScoDoc
|
Fonction permettant de générer une carte SVG de l'API de ScoDoc
|
||||||
Elle récupère les routes de l'API et les transforme en un arbre de Token
|
Elle récupère les routes de l'API et les transforme en un arbre de Token
|
||||||
puis génère un fichier SVG à partir de cet arbre
|
puis génère un fichier SVG à partir de cet arbre
|
||||||
"""
|
"""
|
||||||
|
|
||||||
api_map, doctable_lines = analyze_api_routes(app, endpoint_start)
|
|
||||||
|
|
||||||
# On génère le SVG à partir de l'arbre de Token
|
# On génère le SVG à partir de l'arbre de Token
|
||||||
generate_svg(api_map.to_svg_group(), "/tmp/api_map.svg")
|
generate_svg(api_map.to_svg_group(), "/tmp/api_map.svg")
|
||||||
print(
|
print(
|
||||||
@ -745,23 +751,6 @@ def _get_doc_lines(keyword, doc_string: str) -> list[str]:
|
|||||||
return kw_lines
|
return kw_lines
|
||||||
|
|
||||||
|
|
||||||
def parse_doc_name(doc_string: str) -> str:
|
|
||||||
"""
|
|
||||||
renvoie le nom de la route à partir de la docstring
|
|
||||||
|
|
||||||
La doc doit contenir des lignes de la forme:
|
|
||||||
|
|
||||||
DOC_ANCHOR
|
|
||||||
----------
|
|
||||||
nom_de_la_route
|
|
||||||
|
|
||||||
Il ne peut y avoir qu'une seule ligne suivant -----
|
|
||||||
|
|
||||||
"""
|
|
||||||
name_lines: list[str] = _get_doc_lines("DOC_ANCHOR", doc_string)
|
|
||||||
return name_lines[0] if name_lines else None
|
|
||||||
|
|
||||||
|
|
||||||
def parse_query_doc(doc_string: str) -> dict[str, str]:
|
def parse_query_doc(doc_string: str) -> dict[str, str]:
|
||||||
"""
|
"""
|
||||||
renvoie un dictionnaire {param: <type:nom_param>} (ex: {assiduite_id : <int:assiduite_id>})
|
renvoie un dictionnaire {param: <type:nom_param>} (ex: {assiduite_id : <int:assiduite_id>})
|
||||||
@ -795,45 +784,28 @@ def parse_query_doc(doc_string: str) -> dict[str, str]:
|
|||||||
return query
|
return query
|
||||||
|
|
||||||
|
|
||||||
def parse_doctable_doc(doc_string: str) -> dict[str, str]:
|
def _gen_table_line(doctable: dict = None):
|
||||||
"""
|
|
||||||
Retourne un dictionnaire représentant les informations du tableau d'api
|
|
||||||
à partir de la doc (DOC-TABLE)
|
|
||||||
|
|
||||||
éléments optionnels:
|
|
||||||
- `permissions` permissions nécessaires pour accéder à la route (ScoView, AbsChange, ...)
|
|
||||||
- `href` nom (sans #) de l'ancre dans la page ScoDoc9API
|
|
||||||
|
|
||||||
DOC-TABLE
|
|
||||||
---------
|
|
||||||
permissions: ScoView
|
|
||||||
href: une-fonction
|
|
||||||
"""
|
|
||||||
|
|
||||||
doc_lines: list[str] = _get_doc_lines("DOC-TABLE", doc_string)
|
|
||||||
table = {}
|
|
||||||
|
|
||||||
# on parcourt les lignes de la doc
|
|
||||||
for line in doc_lines:
|
|
||||||
# On sépare le paramètre et sa valeur
|
|
||||||
param, value = line.split(":")
|
|
||||||
# On met à jour le dictionnaire
|
|
||||||
table[param.strip()] = value.strip()
|
|
||||||
|
|
||||||
return table
|
|
||||||
|
|
||||||
|
|
||||||
def _gen_table_line(
|
|
||||||
nom="", href="", method="", permission="", doctable: dict = None, **kwargs
|
|
||||||
):
|
|
||||||
"""
|
"""
|
||||||
Génère une ligne de tableau markdown
|
Génère une ligne de tableau markdown
|
||||||
|
|
||||||
| nom de la route| methode HTTP| Permission |
|
| nom de la route| methode HTTP| Permission |
|
||||||
"""
|
"""
|
||||||
lien: str = href
|
|
||||||
if "href" in doctable:
|
nom, method, permission = (
|
||||||
lien: str = doctable.get("href")
|
doctable.get("nom", ""),
|
||||||
|
doctable.get("method", ""),
|
||||||
|
doctable.get("permission", ""),
|
||||||
|
)
|
||||||
|
|
||||||
|
if doctable is None:
|
||||||
|
doctable = {}
|
||||||
|
|
||||||
|
lien: str = doctable.get("href", nom)
|
||||||
|
|
||||||
|
doctable["query"]: bool
|
||||||
|
if doctable.get("query") and not lien.endswith("-query"):
|
||||||
|
lien += "-query"
|
||||||
|
|
||||||
nav: str = f"[{nom}]({'#'+lien})"
|
nav: str = f"[{nom}]({'#'+lien})"
|
||||||
|
|
||||||
table: str = "|"
|
table: str = "|"
|
||||||
@ -858,19 +830,58 @@ def _gen_table(lines: list[dict]) -> str:
|
|||||||
"""
|
"""
|
||||||
Génère un tableau markdown à partir d'une liste de lignes
|
Génère un tableau markdown à partir d'une liste de lignes
|
||||||
|
|
||||||
lines : liste de dictionnaire au format :
|
lines : liste de dictionnaire au format doc_lines.
|
||||||
|
|
||||||
- doctable : dict généré par parse_doctable_doc
|
|
||||||
- nom : nom de la fonction associée à la route
|
|
||||||
- method : GET ou POST
|
|
||||||
- permission : Permissions de la route (auto récupérée)
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
table = _gen_table_head()
|
table = _gen_table_head()
|
||||||
table += "\n".join([_gen_table_line(**line) for line in lines])
|
table += "\n".join([_gen_table_line(line) for line in lines])
|
||||||
return table
|
return table
|
||||||
|
|
||||||
|
|
||||||
|
def _gen_csv_line(doc_line: dict) -> str:
|
||||||
|
"""
|
||||||
|
Génère les lignes de tableau csv en fonction d'une route (doc_line)
|
||||||
|
|
||||||
|
format :
|
||||||
|
"entry_name";"url";"permission";"method";"content"
|
||||||
|
"""
|
||||||
|
|
||||||
|
entry_name: str = doc_line.get("nom", "")
|
||||||
|
method: str = doc_line.get("method", "GET")
|
||||||
|
permission: str = (
|
||||||
|
"UsersAdmin" if doc_line.get("permission") != "ScoView" else "ScoView"
|
||||||
|
)
|
||||||
|
|
||||||
|
samples: list[str] = doc_line.get("samples", [])
|
||||||
|
csv_lines: list[str] = []
|
||||||
|
for sample in samples:
|
||||||
|
url, content = sample.split(";", maxsplit=1)
|
||||||
|
csv_line = f'"{entry_name}";"{url}";"{permission}";"{method}";'
|
||||||
|
if content:
|
||||||
|
csv_line += f'"{content}"'
|
||||||
|
csv_lines.append(csv_line)
|
||||||
|
|
||||||
|
return "\n".join(csv_lines)
|
||||||
|
|
||||||
|
|
||||||
|
def _gen_csv(lines: list[dict], filename: str = "/tmp/samples.csv") -> str:
|
||||||
|
"""
|
||||||
|
Génère un fichier csv à partir d'une liste de lignes
|
||||||
|
|
||||||
|
lines : liste de dictionnaire au format doc_lines.
|
||||||
|
"""
|
||||||
|
csv = '"entry_name";"url";"permission";"method";"content"\n'
|
||||||
|
csv += "\n".join(
|
||||||
|
[_gen_csv_line(line) for line in lines if line.get("samples") is not None]
|
||||||
|
)
|
||||||
|
|
||||||
|
with open(filename, "w", encoding="UTF-8") as f:
|
||||||
|
f.write(csv)
|
||||||
|
|
||||||
|
print(
|
||||||
|
f"Les samples ont été générés avec succès. Vous pouvez le consulter à l'adresse suivante : {filename}"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def _write_gen_table(table: str, filename: str = "/tmp/api_table.md"):
|
def _write_gen_table(table: str, filename: str = "/tmp/api_table.md"):
|
||||||
"""Ecriture du fichier md avec la table"""
|
"""Ecriture du fichier md avec la table"""
|
||||||
with open(filename, "w", encoding="UTF-8") as f:
|
with open(filename, "w", encoding="UTF-8") as f:
|
||||||
@ -886,6 +897,16 @@ def doc_route(doctable: dict) -> str:
|
|||||||
jinja_obj.update(doctable)
|
jinja_obj.update(doctable)
|
||||||
jinja_obj["nom"] = doctable["nom"].strip() # on retire les caractères blancs
|
jinja_obj["nom"] = doctable["nom"].strip() # on retire les caractères blancs
|
||||||
|
|
||||||
|
if doctable.get("samples") is not None:
|
||||||
|
jinja_obj["sample"] = {
|
||||||
|
"nom": f"{jinja_obj['nom']}.json",
|
||||||
|
"href": f"{jinja_obj['nom']}.json.md",
|
||||||
|
}
|
||||||
|
|
||||||
|
jinja_obj["query"]: bool
|
||||||
|
if jinja_obj["query"]:
|
||||||
|
jinja_obj["nom"] += "(-query)"
|
||||||
|
|
||||||
if doctable.get("params"):
|
if doctable.get("params"):
|
||||||
jinja_obj["params"] = []
|
jinja_obj["params"] = []
|
||||||
for param in doctable["params"]:
|
for param in doctable["params"]:
|
||||||
@ -901,43 +922,41 @@ def doc_route(doctable: dict) -> str:
|
|||||||
descr = "\n".join(s for s in doctable["description"])
|
descr = "\n".join(s for s in doctable["description"])
|
||||||
jinja_obj["description"] = descr.strip()
|
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("doc/apidoc.j2", doc=jinja_obj)
|
return render_template("doc/apidoc.j2", doc=jinja_obj)
|
||||||
|
|
||||||
|
|
||||||
def gen_api_doc(app, endpoint_start="api."):
|
def gen_api_doc(app, endpoint_start="api."):
|
||||||
"commande gen-api-doc"
|
"commande gen-api-doc"
|
||||||
_, doctable_lines = analyze_api_routes(app, endpoint_start)
|
api_map, doctable_lines = analyze_api_routes(app, endpoint_start)
|
||||||
|
|
||||||
mddoc: str = ""
|
mddoc: str = ""
|
||||||
|
|
||||||
categories: dict = {}
|
categories: dict = {}
|
||||||
for value in doctable_lines.values():
|
for value in doctable_lines.values():
|
||||||
category = value["category"]
|
category = value["category"].capitalize()
|
||||||
if category not in categories:
|
if category not in categories:
|
||||||
categories[category] = []
|
categories[category] = []
|
||||||
categories[category].append(value)
|
categories[category].append(value)
|
||||||
|
|
||||||
# sort categories by name
|
# sort categories by name
|
||||||
categories: dict = dict(sorted(categories.items(), key=lambda x: x[0].capitalize()))
|
categories: dict = dict(
|
||||||
|
sorted(categories.items(), key=lambda x: strip_accents(x[0]))
|
||||||
|
)
|
||||||
|
|
||||||
category: str
|
category: str
|
||||||
routes: list[dict]
|
routes: list[dict]
|
||||||
for category, routes in categories.items():
|
for category, routes in categories.items():
|
||||||
# sort routes by name
|
# sort routes by name
|
||||||
routes.sort(key=lambda x: x["nom"])
|
routes.sort(key=lambda x: strip_accents(x["nom"]))
|
||||||
|
|
||||||
mddoc += f"### API {category.capitalize()}\n\n"
|
mddoc += f"### API {category.capitalize()}\n\n"
|
||||||
for route in routes:
|
for route in routes:
|
||||||
mddoc += doc_route(route)
|
mddoc += doc_route(route)
|
||||||
mddoc += "\n\n"
|
mddoc += "\n\n"
|
||||||
|
|
||||||
table_api = gen_api_map(app, endpoint_start=endpoint_start)
|
table_api = gen_api_map(api_map, doctable_lines)
|
||||||
mdpage = render_template("doc/ScoDoc9API.j2", doc_api=mddoc, table_api=table_api)
|
mdpage = render_template("doc/ScoDoc9API.j2", doc_api=mddoc, table_api=table_api)
|
||||||
|
_gen_csv(list(doctable_lines.values()))
|
||||||
|
|
||||||
fname = "/tmp/ScoDoc9API.md"
|
fname = "/tmp/ScoDoc9API.md"
|
||||||
with open(fname, "w", encoding="utf-8") as f:
|
with open(fname, "w", encoding="utf-8") as f:
|
||||||
|
Loading…
Reference in New Issue
Block a user