forked from ScoDoc/ScoDoc
Merge branch 'master' of https://scodoc.org/git/viennet/ScoDoc into revamp
This commit is contained in:
commit
7ff0fd39fb
@ -19,7 +19,8 @@ import app.scodoc.sco_assiduites as scass
|
||||
import app.scodoc.sco_utils as scu
|
||||
from app.api import api_bp as bp
|
||||
from app.api import api_web_bp, get_model_api_object, tools
|
||||
from app.decorators import permission_required, scodoc
|
||||
from app.api import api_permission_required as permission_required
|
||||
from app.decorators import scodoc
|
||||
from app.models import (
|
||||
Assiduite,
|
||||
Evaluation,
|
||||
@ -47,6 +48,8 @@ def assiduite(assiduite_id: int = None):
|
||||
"""Retourne un objet assiduité à partir de son id
|
||||
|
||||
Exemple de résultat:
|
||||
|
||||
```json
|
||||
{
|
||||
"assiduite_id": 1,
|
||||
"etudid": 2,
|
||||
@ -55,11 +58,17 @@ 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,
|
||||
}
|
||||
```
|
||||
|
||||
SAMPLES
|
||||
-------
|
||||
/assiduite/1;
|
||||
|
||||
"""
|
||||
|
||||
return get_model_api_object(Assiduite, assiduite_id, Identite)
|
||||
@ -77,15 +86,23 @@ def assiduite(assiduite_id: int = None):
|
||||
@permission_required(Permission.ScoView)
|
||||
@as_json
|
||||
def assiduite_justificatifs(assiduite_id: int = None, long: bool = False):
|
||||
"""Retourne la liste des justificatifs qui justifie cette assiduitée
|
||||
"""Retourne la liste des justificatifs qui justifient cette assiduité.
|
||||
|
||||
Exemple de résultat:
|
||||
|
||||
```json
|
||||
[
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
...
|
||||
]
|
||||
```
|
||||
SAMPLES
|
||||
-------
|
||||
/assiduite/1/justificatifs;
|
||||
/assiduite/1/justificatifs/long;
|
||||
|
||||
"""
|
||||
|
||||
return get_assiduites_justif(assiduite_id, long)
|
||||
@ -123,45 +140,7 @@ def assiduites_count(
|
||||
etudid: int = None, nip: str = None, ine: str = None, with_query: bool = False
|
||||
):
|
||||
"""
|
||||
Retourne le nombre d'assiduités d'un étudiant
|
||||
chemin : /assiduites/<int:etudid>/count
|
||||
|
||||
Un filtrage peut être donné avec une query
|
||||
chemin : /assiduites/<int:etudid>/count/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
|
||||
Retourne le nombre d'assiduités d'un étudiant.
|
||||
|
||||
QUERY
|
||||
-----
|
||||
@ -175,6 +154,25 @@ def assiduites_count(
|
||||
metric:<array[string]:metric>
|
||||
split:<bool: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
|
||||
|
||||
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
|
||||
@ -230,39 +228,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/<int:etudid>
|
||||
|
||||
Un filtrage peut être donné avec une query
|
||||
chemin : /assiduites/<int:etudid>/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
|
||||
-----
|
||||
@ -273,6 +238,25 @@ def assiduites(etudid: int = None, nip=None, ine=None, with_query: bool = False)
|
||||
date_fin:<string:date_fin_iso>
|
||||
etat:<array[string]:etat>
|
||||
formsemestre_id:<int:formsemestre_id>
|
||||
with_justifs:<bool:with_justifs>
|
||||
|
||||
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é
|
||||
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=;
|
||||
|
||||
"""
|
||||
|
||||
@ -328,7 +312,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,
|
||||
@ -341,6 +327,12 @@ def assiduites_evaluations(etudid: int = None, nip=None, ine=None):
|
||||
}
|
||||
]
|
||||
|
||||
SAMPLES
|
||||
-------
|
||||
/assiduites/1/evaluations;
|
||||
|
||||
```
|
||||
|
||||
"""
|
||||
# Récupération de l'étudiant
|
||||
etud: Identite = tools.get_etud(etudid, nip, ine)
|
||||
@ -367,7 +359,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
|
||||
{
|
||||
"<etudid>" : [
|
||||
{
|
||||
@ -376,6 +371,11 @@ def evaluation_assiduites(evaluation_id):
|
||||
},
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
CATEGORY
|
||||
--------
|
||||
Évaluations
|
||||
"""
|
||||
# Récupération de l'évaluation
|
||||
try:
|
||||
@ -404,37 +404,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
|
||||
-----
|
||||
@ -444,8 +413,25 @@ def assiduites_group(with_query: bool = False):
|
||||
date_debut:<string:date_debut_iso>
|
||||
date_fin:<string:date_fin_iso>
|
||||
etat:<array[string]:etat>
|
||||
etudids:<array[int]:etudids
|
||||
etudids:<array[int]:etudids>
|
||||
formsemestre_id:<int:formsemestre_id>
|
||||
with_justif:<bool:with_justif>
|
||||
|
||||
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é
|
||||
with_justifs:ajoute les justificatifs liés à l'assiduité
|
||||
|
||||
SAMPLES
|
||||
-------
|
||||
/assiduites/group/query?etudids=1,2,3;
|
||||
|
||||
"""
|
||||
|
||||
@ -506,6 +492,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:<int:user_id>
|
||||
@ -514,6 +501,23 @@ def assiduites_formsemestre(formsemestre_id: int, with_query: bool = False):
|
||||
date_debut:<string:date_debut_iso>
|
||||
date_fin:<string:date_fin_iso>
|
||||
etat:<array[string]: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é
|
||||
|
||||
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
|
||||
@ -577,6 +581,25 @@ def assiduites_formsemestre_count(
|
||||
formsemestre_id:<int:formsemestre_id>
|
||||
metric:<array[string]:metric>
|
||||
split:<bool: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
|
||||
|
||||
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
|
||||
@ -628,7 +651,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,
|
||||
@ -644,6 +670,12 @@ 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
|
||||
@ -697,7 +729,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,
|
||||
@ -710,12 +745,17 @@ def assiduites_create():
|
||||
"date_fin": str,
|
||||
"etat": str,
|
||||
"etudid":int,
|
||||
|
||||
"moduleimpl_id": int,
|
||||
"desc":str,
|
||||
}
|
||||
...
|
||||
]
|
||||
```
|
||||
|
||||
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""}]
|
||||
|
||||
"""
|
||||
|
||||
@ -886,13 +926,18 @@ def assiduite_delete():
|
||||
"""
|
||||
Suppression d'une assiduité à partir de son id
|
||||
|
||||
Forme des données envoyées :
|
||||
|
||||
DATA
|
||||
----
|
||||
```json
|
||||
[
|
||||
<assiduite_id:int>,
|
||||
...
|
||||
]
|
||||
```
|
||||
|
||||
SAMPLES
|
||||
-------
|
||||
/assiduite/delete;[2,2,3]
|
||||
|
||||
"""
|
||||
# Récupération des ids envoyés dans la liste
|
||||
@ -967,13 +1012,24 @@ 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
|
||||
}
|
||||
```
|
||||
|
||||
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
|
||||
@ -1015,7 +1071,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,
|
||||
@ -1025,6 +1084,13 @@ def assiduites_edit():
|
||||
"est_just"?: bool
|
||||
}
|
||||
]
|
||||
```
|
||||
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)
|
||||
|
||||
|
@ -6,6 +6,11 @@
|
||||
|
||||
"""
|
||||
API : billets d'absences
|
||||
|
||||
CATEGORY
|
||||
--------
|
||||
Billets d'absence
|
||||
|
||||
"""
|
||||
|
||||
from flask import g, request
|
||||
@ -20,6 +25,7 @@ from app.models import BilletAbsence
|
||||
from app.models.etudiants import Identite
|
||||
from app.scodoc import sco_abs_billets
|
||||
from app.scodoc.sco_permissions import Permission
|
||||
from app.scodoc import sco_utils as scu
|
||||
|
||||
|
||||
@bp.route("/billets_absence/etudiant/<int:etudid>")
|
||||
@ -29,7 +35,7 @@ from app.scodoc.sco_permissions import Permission
|
||||
@permission_required(Permission.ScoView)
|
||||
@as_json
|
||||
def billets_absence_etudiant(etudid: int):
|
||||
"""Liste des billets d'absence pour cet étudiant"""
|
||||
"""Liste des billets d'absence pour cet étudiant."""
|
||||
billets = sco_abs_billets.query_billets_etud(etudid)
|
||||
return [billet.to_dict() for billet in billets]
|
||||
|
||||
@ -41,13 +47,30 @@ def billets_absence_etudiant(etudid: int):
|
||||
@permission_required(Permission.AbsAddBillet)
|
||||
@as_json
|
||||
def billets_absence_create():
|
||||
"""Ajout d'un billet d'absence"""
|
||||
"""Ajout d'un billet d'absence. Renvoie le billet créé en json.
|
||||
|
||||
DATA
|
||||
----
|
||||
```json
|
||||
{
|
||||
"etudid" : int,
|
||||
"abs_begin" : date_iso,
|
||||
"abs_end" : date_iso,
|
||||
"description" : string,
|
||||
"justified" : bool
|
||||
}
|
||||
```
|
||||
|
||||
SAMPLES
|
||||
-------
|
||||
/billets_absence/create;{""etudid"":""1"",""abs_begin"":""2023-10-27T10:00"",""abs_end"":""2023-10-28T10:00"",""description"":""grave malade"",""justified"":""1""}
|
||||
"""
|
||||
data = request.get_json(force=True) # may raise 400 Bad Request
|
||||
etudid = data.get("etudid")
|
||||
abs_begin = data.get("abs_begin")
|
||||
abs_end = data.get("abs_end")
|
||||
description = data.get("description", "")
|
||||
justified = data.get("justified", False)
|
||||
justified = scu.to_bool(data.get("justified", False))
|
||||
if None in (etudid, abs_begin, abs_end):
|
||||
return json_error(
|
||||
404, message="Paramètre manquant: etudid, abs_begin, abs_end requis"
|
||||
|
@ -9,6 +9,11 @@
|
||||
|
||||
Note: les routes /departement[s] sont publiées sur l'API (/ScoDoc/api/),
|
||||
mais évidemment pas sur l'API web (/ScoDoc/<dept>/api).
|
||||
|
||||
CATEGORY
|
||||
--------
|
||||
Département
|
||||
|
||||
"""
|
||||
from datetime import datetime
|
||||
|
||||
@ -27,24 +32,19 @@ from app.scodoc.sco_permissions import Permission
|
||||
from app.scodoc.sco_utils import json_error
|
||||
|
||||
|
||||
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")
|
||||
@login_required
|
||||
@scodoc
|
||||
@permission_required(Permission.ScoView)
|
||||
@as_json
|
||||
def departements_list():
|
||||
"""Liste les départements"""
|
||||
"""Liste tous les départements.
|
||||
|
||||
SAMPLES
|
||||
-------
|
||||
/departements;
|
||||
|
||||
"""
|
||||
return [dept.to_dict(with_dept_name=True) for dept in Departement.query]
|
||||
|
||||
|
||||
@ -54,7 +54,13 @@ def departements_list():
|
||||
@permission_required(Permission.ScoView)
|
||||
@as_json
|
||||
def departements_ids():
|
||||
"""Liste des ids de départements"""
|
||||
"""Liste des ids de tous les départements.
|
||||
|
||||
SAMPLES
|
||||
-------
|
||||
/departements_ids;
|
||||
|
||||
"""
|
||||
return [dept.id for dept in Departement.query]
|
||||
|
||||
|
||||
@ -67,15 +73,10 @@ def departement_by_acronym(acronym: str):
|
||||
"""
|
||||
Info sur un département. Accès par acronyme.
|
||||
|
||||
Exemple de résultat :
|
||||
{
|
||||
"id": 1,
|
||||
"acronym": "TAPI",
|
||||
"dept_name" : "TEST",
|
||||
"description": null,
|
||||
"visible": true,
|
||||
"date_creation": "Fri, 15 Apr 2022 12:19:28 GMT"
|
||||
}
|
||||
SAMPLES
|
||||
-------
|
||||
/departement/TAPI;
|
||||
|
||||
"""
|
||||
dept = Departement.query.filter_by(acronym=acronym).first_or_404()
|
||||
return dept.to_dict(with_dept_name=True)
|
||||
@ -86,9 +87,14 @@ def departement_by_acronym(acronym: str):
|
||||
@scodoc
|
||||
@permission_required(Permission.ScoView)
|
||||
@as_json
|
||||
def departement_by_id(dept_id: int):
|
||||
def departement_get(dept_id: int):
|
||||
"""
|
||||
Info sur un département. Accès par id.
|
||||
|
||||
SAMPLES
|
||||
-------
|
||||
/departement/id/1;
|
||||
|
||||
"""
|
||||
dept = Departement.query.get_or_404(dept_id)
|
||||
return dept.to_dict()
|
||||
@ -102,11 +108,19 @@ def departement_by_id(dept_id: int):
|
||||
def departement_create():
|
||||
"""
|
||||
Création d'un département.
|
||||
The request content type should be "application/json":
|
||||
Le content type doit être `application/json`.
|
||||
DATA
|
||||
----
|
||||
```json
|
||||
{
|
||||
"acronym": str,
|
||||
"visible":bool,
|
||||
"visible": bool,
|
||||
}
|
||||
```
|
||||
|
||||
SAMPLES
|
||||
-------
|
||||
/departement/create;{""acronym"":""MYDEPT"",""visible"":""1""}
|
||||
"""
|
||||
data = request.get_json(force=True) # may raise 400 Bad Request
|
||||
acronym = str(data.get("acronym", ""))
|
||||
@ -130,10 +144,12 @@ def departement_create():
|
||||
@as_json
|
||||
def departement_edit(acronym):
|
||||
"""
|
||||
Edition d'un département: seul visible peut être modifié
|
||||
The request content type should be "application/json":
|
||||
Édition d'un département: seul le champ `visible` peut être modifié.
|
||||
|
||||
DATA
|
||||
----
|
||||
{
|
||||
"visible":bool,
|
||||
"visible": bool,
|
||||
}
|
||||
"""
|
||||
dept = Departement.query.filter_by(acronym=acronym).first_or_404()
|
||||
@ -155,7 +171,7 @@ def departement_edit(acronym):
|
||||
@permission_required(Permission.ScoSuperAdmin)
|
||||
def departement_delete(acronym):
|
||||
"""
|
||||
Suppression d'un département.
|
||||
Suppression d'un département identifié par son acronyme.
|
||||
"""
|
||||
dept = Departement.query.filter_by(acronym=acronym).first_or_404()
|
||||
acronym = dept.acronym
|
||||
@ -172,25 +188,16 @@ def departement_delete(acronym):
|
||||
@as_json
|
||||
def departement_etudiants(acronym: str):
|
||||
"""
|
||||
Retourne la liste des étudiants d'un département
|
||||
Retourne la liste des étudiants d'un département.
|
||||
|
||||
acronym: l'acronyme d'un département
|
||||
PARAMS
|
||||
------
|
||||
acronym : l'acronyme d'un département
|
||||
|
||||
SAMPLES
|
||||
-------
|
||||
/departement/TAPI/etudiants;
|
||||
|
||||
Exemple de résultat :
|
||||
[
|
||||
{
|
||||
"civilite": "M",
|
||||
"code_ine": "7899X61616",
|
||||
"code_nip": "F6777H88",
|
||||
"date_naissance": null,
|
||||
"email": "toto@toto.fr",
|
||||
"emailperso": null,
|
||||
"etudid": 18,
|
||||
"nom": "MOREL",
|
||||
"prenom": "JACQUES"
|
||||
},
|
||||
...
|
||||
]
|
||||
"""
|
||||
dept = Departement.query.filter_by(acronym=acronym).first_or_404()
|
||||
return [etud.to_dict_short() for etud in dept.etudiants]
|
||||
@ -215,7 +222,13 @@ def departement_etudiants_by_id(dept_id: int):
|
||||
@permission_required(Permission.ScoView)
|
||||
@as_json
|
||||
def departement_formsemestres_ids(acronym: str):
|
||||
"""liste des ids formsemestre du département"""
|
||||
"""Liste des ids de tous les formsemestres du département.
|
||||
|
||||
SAMPLES
|
||||
-------
|
||||
/departement/TAPI/formsemestres_ids;
|
||||
|
||||
"""
|
||||
dept = Departement.query.filter_by(acronym=acronym).first_or_404()
|
||||
return [formsemestre.id for formsemestre in dept.formsemestres]
|
||||
|
||||
@ -226,7 +239,13 @@ def departement_formsemestres_ids(acronym: str):
|
||||
@permission_required(Permission.ScoView)
|
||||
@as_json
|
||||
def departement_formsemestres_ids_by_id(dept_id: int):
|
||||
"""liste des ids formsemestre du département"""
|
||||
"""Liste des ids de tous les formsemestres du département.
|
||||
|
||||
SAMPLES
|
||||
-------
|
||||
/departement/id/1/formsemestres_ids;
|
||||
|
||||
"""
|
||||
dept = Departement.query.get_or_404(dept_id)
|
||||
return [formsemestre.id for formsemestre in dept.formsemestres]
|
||||
|
||||
@ -239,7 +258,7 @@ def departement_formsemestres_ids_by_id(dept_id: int):
|
||||
@as_json
|
||||
def departement_formsemestres_courants(acronym: str = "", dept_id: int | None = None):
|
||||
"""
|
||||
Liste les semestres du département indiqué (par son acronyme ou son id)
|
||||
Liste les formsemestres du département indiqué (par son acronyme ou son id)
|
||||
contenant la date courante, ou à défaut celle indiquée en argument
|
||||
(au format ISO).
|
||||
|
||||
@ -247,6 +266,9 @@ def departement_formsemestres_courants(acronym: str = "", dept_id: int | None =
|
||||
-----
|
||||
date_courante:<string:date_courante>
|
||||
|
||||
SAMPLES
|
||||
-------
|
||||
/departement/id/1/formsemestres_courants?date_courante=2022-01-01
|
||||
"""
|
||||
dept = (
|
||||
Departement.query.filter_by(acronym=acronym).first_or_404()
|
||||
|
@ -6,6 +6,10 @@
|
||||
|
||||
"""
|
||||
API : accès aux étudiants
|
||||
|
||||
CATEGORY
|
||||
--------
|
||||
Étudiants
|
||||
"""
|
||||
from datetime import datetime
|
||||
from operator import attrgetter
|
||||
@ -38,9 +42,8 @@ from app.scodoc import sco_groups
|
||||
from app.scodoc.sco_bulletins import do_formsemestre_bulletinetud
|
||||
from app.scodoc import sco_etud
|
||||
from app.scodoc.sco_permissions import Permission
|
||||
from app.scodoc import sco_photos
|
||||
from app.scodoc.sco_utils import json_error, suppress_accents
|
||||
|
||||
import app.scodoc.sco_photos as sco_photos
|
||||
import app.scodoc.sco_utils as scu
|
||||
|
||||
# Un exemple:
|
||||
@ -98,25 +101,16 @@ def etudiants_courants(long: bool = False):
|
||||
et les formsemestres contenant la date courante,
|
||||
ou à défaut celle indiquée en argument (au format ISO).
|
||||
|
||||
En format "long": voir l'exemple.
|
||||
|
||||
QUERY
|
||||
-----
|
||||
date_courante:<string:date_courante>
|
||||
|
||||
Exemple de résultat :
|
||||
[
|
||||
{
|
||||
"id": 1234,
|
||||
"code_nip": "12345678",
|
||||
"code_ine": null,
|
||||
"nom": "JOHN",
|
||||
"nom_usuel": None,
|
||||
"prenom": "DEUF",
|
||||
"civilite": "M",
|
||||
}
|
||||
...
|
||||
]
|
||||
|
||||
En format "long": voir documentation.
|
||||
SAMPLES
|
||||
-------
|
||||
/etudiants/courants?date_courante=2022-05-01;
|
||||
/etudiants/courants/long?date_courante=2022-05-01;
|
||||
|
||||
"""
|
||||
allowed_depts = current_user.get_depts_with_permission(Permission.ScoView)
|
||||
@ -160,10 +154,13 @@ def etudiant(etudid: int = None, nip: str = None, ine: str = None):
|
||||
"""
|
||||
Retourne les informations de l'étudiant correspondant, ou 404 si non trouvé.
|
||||
|
||||
PARAMS
|
||||
------
|
||||
etudid : l'etudid de l'étudiant
|
||||
nip : le code nip de l'étudiant
|
||||
ine : le code ine de l'étudiant
|
||||
|
||||
`etudid` est unique dans la base (tous départements).
|
||||
Les codes INE et NIP sont uniques au sein d'un département.
|
||||
Si plusieurs objets ont le même code, on ramène le plus récemment inscrit.
|
||||
"""
|
||||
@ -197,6 +194,8 @@ def etudiant_get_photo_image(etudid: int = None, nip: str = None, ine: str = Non
|
||||
-----
|
||||
size:<string:size>
|
||||
|
||||
PARAMS
|
||||
------
|
||||
etudid : l'etudid de l'étudiant
|
||||
nip : le code nip de l'étudiant
|
||||
ine : le code ine de l'étudiant
|
||||
@ -269,9 +268,12 @@ def etudiant_set_photo_image(etudid: int = None):
|
||||
@as_json
|
||||
def etudiants(etudid: int = None, nip: str = None, ine: str = None):
|
||||
"""
|
||||
Info sur le ou les étudiants correspondant. Comme /etudiant mais renvoie
|
||||
toujours une liste.
|
||||
Info sur le ou les étudiants correspondants.
|
||||
|
||||
Comme `/etudiant` mais renvoie toujours une liste.
|
||||
|
||||
Si non trouvé, liste vide, pas d'erreur.
|
||||
|
||||
Dans 99% des cas, la liste contient un seul étudiant, mais si l'étudiant a
|
||||
été inscrit dans plusieurs départements, on a plusieurs objets (1 par dept.).
|
||||
"""
|
||||
@ -304,8 +306,9 @@ def etudiants(etudid: int = None, nip: str = None, ine: str = None):
|
||||
@permission_required(Permission.ScoView)
|
||||
@as_json
|
||||
def etudiants_by_name(start: str = "", min_len=3, limit=32):
|
||||
"""Liste des étudiants dont le nom débute par start.
|
||||
Si start fait moins de min_len=3 caractères, liste vide.
|
||||
"""Liste des étudiants dont le nom débute par `start`.
|
||||
|
||||
Si `start` fait moins de `min_len=3` caractères, liste vide.
|
||||
La casse et les accents sont ignorés.
|
||||
"""
|
||||
if len(start) < min_len:
|
||||
@ -340,13 +343,13 @@ def etudiants_by_name(start: str = "", min_len=3, limit=32):
|
||||
@as_json
|
||||
def etudiant_formsemestres(etudid: int = None, nip: int = None, ine: int = None):
|
||||
"""
|
||||
Liste des semestres qu'un étudiant a suivi, triés par ordre chronologique.
|
||||
Liste des formsemestres qu'un étudiant a suivi, triés par ordre chronologique.
|
||||
Accès par etudid, nip ou ine.
|
||||
|
||||
Attention, si accès via NIP ou INE, les semestres peuvent être de départements
|
||||
Attention, si accès via NIP ou INE, les formsemestres peuvent être de départements
|
||||
différents (si l'étudiant a changé de département). L'id du département est `dept_id`.
|
||||
|
||||
Si accès par département, ne retourne que les formsemestre suivis dans le département.
|
||||
Si accès par département, ne retourne que les formsemestres suivis dans le département.
|
||||
"""
|
||||
if etudid is not None:
|
||||
q_etud = Identite.query.filter_by(id=etudid)
|
||||
@ -414,13 +417,18 @@ def bulletin(
|
||||
"""
|
||||
Retourne le bulletin d'un étudiant dans un formsemestre.
|
||||
|
||||
PARAMS
|
||||
------
|
||||
formsemestre_id : l'id d'un formsemestre
|
||||
code_type : "etudid", "nip" ou "ine"
|
||||
code : valeur du code INE, NIP ou etudid, selon code_type.
|
||||
version : type de bulletin (par défaut, "selectedevals"): short, long, selectedevals, butcourt
|
||||
pdf : si spécifié, bulletin au format PDF (et non JSON).
|
||||
|
||||
Exemple de résultat : voir https://scodoc.org/ScoDoc9API/#bulletin
|
||||
SAMPLES
|
||||
-------
|
||||
/etudiant/etudid/1/formsemestre/1/bulletin
|
||||
|
||||
"""
|
||||
if version == "pdf":
|
||||
version = "long"
|
||||
@ -474,34 +482,14 @@ def etudiant_groups(formsemestre_id: int, etudid: int = None):
|
||||
"""
|
||||
Retourne la liste des groupes auxquels appartient l'étudiant dans le formsemestre indiqué
|
||||
|
||||
PARAMS
|
||||
------
|
||||
formsemestre_id : l'id d'un formsemestre
|
||||
etudid : l'etudid d'un étudiant
|
||||
|
||||
Exemple de résultat :
|
||||
[
|
||||
{
|
||||
"partition_id": 1,
|
||||
"id": 1,
|
||||
"formsemestre_id": 1,
|
||||
"partition_name": null,
|
||||
"numero": 0,
|
||||
"bul_show_rank": false,
|
||||
"show_in_lists": true,
|
||||
"group_id": 1,
|
||||
"group_name": null
|
||||
},
|
||||
{
|
||||
"partition_id": 2,
|
||||
"id": 2,
|
||||
"formsemestre_id": 1,
|
||||
"partition_name": "TD",
|
||||
"numero": 1,
|
||||
"bul_show_rank": false,
|
||||
"show_in_lists": true,
|
||||
"group_id": 2,
|
||||
"group_name": "A"
|
||||
}
|
||||
]
|
||||
SAMPLES
|
||||
-------
|
||||
/etudiant/etudid/1/formsemestre/1/groups
|
||||
"""
|
||||
query = FormSemestre.query.filter_by(id=formsemestre_id)
|
||||
if g.scodoc_dept:
|
||||
@ -529,9 +517,12 @@ def etudiant_groups(formsemestre_id: int, etudid: int = None):
|
||||
@permission_required(Permission.EtudInscrit)
|
||||
@as_json
|
||||
def etudiant_create(force=False):
|
||||
"""Création d'un nouvel étudiant
|
||||
"""Création d'un nouvel étudiant.
|
||||
|
||||
Si force, crée même si homonymie détectée.
|
||||
|
||||
L'étudiant créé n'est pas inscrit à un semestre.
|
||||
|
||||
Champs requis: nom, prenom (sauf si config sans prénom), dept (string:acronyme)
|
||||
"""
|
||||
args = request.get_json(force=True) # may raise 400 Bad Request
|
||||
@ -599,7 +590,17 @@ def etudiant_edit(
|
||||
code_type: str = "etudid",
|
||||
code: str = None,
|
||||
):
|
||||
"""Edition des données étudiant (identité, admission, adresses)"""
|
||||
"""Édition des données étudiant (identité, admission, adresses).
|
||||
|
||||
PARAMS
|
||||
------
|
||||
`code_type`: le type du code, `etudid`, `ine` ou `nip`.
|
||||
`code`: la valeur du code
|
||||
|
||||
SAMPLES
|
||||
-------
|
||||
/etudiant/ine/INE1/edit;{""prenom"":""Nouveau Prénom"", ""adresses"":[{""email"":""nouvelle@adresse.fr""}]}
|
||||
"""
|
||||
ok, etud = _get_etud_by_code(code_type, code, g.scodoc_dept)
|
||||
if not ok:
|
||||
return etud # json error
|
||||
@ -638,7 +639,27 @@ def etudiant_annotation(
|
||||
code_type: str = "etudid",
|
||||
code: str = None,
|
||||
):
|
||||
"""Ajout d'une annotation sur un étudiant"""
|
||||
"""Ajout d'une annotation sur un étudiant.
|
||||
|
||||
Renvoie l'annotation créée.
|
||||
|
||||
PARAMS
|
||||
------
|
||||
`code_type`: le type du code, `etudid`, `ine` ou `nip`.
|
||||
`code`: la valeur du code
|
||||
|
||||
DATA
|
||||
----
|
||||
```json
|
||||
{
|
||||
"comment" : string
|
||||
}
|
||||
```
|
||||
|
||||
SAMPLES
|
||||
-------
|
||||
/etudiant/etudid/1/annotation;{""comment"":""une annotation sur l'étudiant""}
|
||||
"""
|
||||
if not current_user.has_permission(Permission.ViewEtudData):
|
||||
return json_error(403, "non autorisé (manque ViewEtudData)")
|
||||
ok, etud = _get_etud_by_code(code_type, code, g.scodoc_dept)
|
||||
@ -675,7 +696,13 @@ def etudiant_annotation_delete(
|
||||
code_type: str = "etudid", code: str = None, annotation_id: int = None
|
||||
):
|
||||
"""
|
||||
Suppression d'une annotation
|
||||
Suppression d'une annotation. On spécifie l'étudiant et l'id de l'annotation.
|
||||
|
||||
PARAMS
|
||||
------
|
||||
`code_type`: le type du code, `etudid`, `ine` ou `nip`.
|
||||
`code`: la valeur du code
|
||||
`annotation_id` : id de l'annotation
|
||||
"""
|
||||
ok, etud = _get_etud_by_code(code_type, code, g.scodoc_dept)
|
||||
if not ok:
|
||||
|
@ -6,6 +6,10 @@
|
||||
|
||||
"""
|
||||
ScoDoc 9 API : accès aux évaluations
|
||||
|
||||
CATEGORY
|
||||
--------
|
||||
Évaluations
|
||||
"""
|
||||
from flask import g, request
|
||||
from flask_json import as_json
|
||||
@ -32,24 +36,28 @@ import app.scodoc.sco_utils as scu
|
||||
def get_evaluation(evaluation_id: int):
|
||||
"""Description d'une évaluation.
|
||||
|
||||
DATA
|
||||
----
|
||||
```json
|
||||
{
|
||||
'coefficient': 1.0,
|
||||
'date_debut': '2016-01-04T08:30:00',
|
||||
'date_fin': '2016-01-04T12:30:00',
|
||||
'description': 'TP NI9219 Température',
|
||||
'evaluation_type': 0,
|
||||
'id': 15797,
|
||||
'moduleimpl_id': 1234,
|
||||
'note_max': 20.0,
|
||||
'numero': 3,
|
||||
'poids': {
|
||||
'UE1.1': 1.0,
|
||||
'UE1.2': 1.0,
|
||||
'UE1.3': 1.0
|
||||
},
|
||||
'publish_incomplete': False,
|
||||
'visibulletin': True
|
||||
}
|
||||
'coefficient': 1.0,
|
||||
'date_debut': '2016-01-04T08:30:00',
|
||||
'date_fin': '2016-01-04T12:30:00',
|
||||
'description': 'TP Température',
|
||||
'evaluation_type': 0,
|
||||
'id': 15797,
|
||||
'moduleimpl_id': 1234,
|
||||
'note_max': 20.0,
|
||||
'numero': 3,
|
||||
'poids': {
|
||||
'UE1.1': 1.0,
|
||||
'UE1.2': 1.0,
|
||||
'UE1.3': 1.0
|
||||
},
|
||||
'publish_incomplete': False,
|
||||
'visibulletin': True
|
||||
}
|
||||
```
|
||||
"""
|
||||
query = Evaluation.query.filter_by(id=evaluation_id)
|
||||
if g.scodoc_dept:
|
||||
@ -70,11 +78,15 @@ def get_evaluation(evaluation_id: int):
|
||||
@as_json
|
||||
def moduleimpl_evaluations(moduleimpl_id: int):
|
||||
"""
|
||||
Retourne la liste des évaluations d'un moduleimpl
|
||||
Retourne la liste des évaluations d'un moduleimpl.
|
||||
|
||||
PARAMS
|
||||
------
|
||||
moduleimpl_id : l'id d'un moduleimpl
|
||||
|
||||
Exemple de résultat : voir /evaluation
|
||||
SAMPLES
|
||||
-------
|
||||
/moduleimpl/1/evaluations
|
||||
"""
|
||||
modimpl = ModuleImpl.get_modimpl(moduleimpl_id)
|
||||
return [evaluation.to_dict_api() for evaluation in modimpl.evaluations]
|
||||
@ -88,30 +100,15 @@ def moduleimpl_evaluations(moduleimpl_id: int):
|
||||
@as_json
|
||||
def evaluation_notes(evaluation_id: int):
|
||||
"""
|
||||
Retourne la liste des notes de l'évaluation
|
||||
Retourne la liste des notes de l'évaluation.
|
||||
|
||||
PARAMS
|
||||
------
|
||||
evaluation_id : l'id de l'évaluation
|
||||
|
||||
Exemple de résultat :
|
||||
{
|
||||
"11": {
|
||||
"etudid": 11,
|
||||
"evaluation_id": 1,
|
||||
"value": 15.0,
|
||||
"comment": "",
|
||||
"date": "Wed, 20 Apr 2022 06:49:05 GMT",
|
||||
"uid": 2
|
||||
},
|
||||
"12": {
|
||||
"etudid": 12,
|
||||
"evaluation_id": 1,
|
||||
"value": 12.0,
|
||||
"comment": "",
|
||||
"date": "Wed, 20 Apr 2022 06:49:06 GMT",
|
||||
"uid": 2
|
||||
},
|
||||
...
|
||||
}
|
||||
SAMPLES
|
||||
-------
|
||||
/evaluation/2/notes;
|
||||
"""
|
||||
query = Evaluation.query.filter_by(id=evaluation_id)
|
||||
if g.scodoc_dept:
|
||||
@ -145,17 +142,26 @@ def evaluation_notes(evaluation_id: int):
|
||||
@as_json
|
||||
def evaluation_set_notes(evaluation_id: int): # evaluation-notes-set
|
||||
"""Écriture de notes dans une évaluation.
|
||||
The request content type should be "application/json",
|
||||
and contains:
|
||||
|
||||
DATA
|
||||
----
|
||||
```json
|
||||
{
|
||||
'notes' : [ [etudid, value], ... ],
|
||||
'comment' : optional string
|
||||
}
|
||||
Result:
|
||||
- nb_changed: nombre de notes changées
|
||||
- nb_suppress: nombre de notes effacées
|
||||
```
|
||||
|
||||
Résultat:
|
||||
|
||||
- etudids_changed: étudiants dont la note est modifiée
|
||||
- etudids_with_decision: liste des etudiants dont la note a changé
|
||||
alors qu'ils ont une décision de jury enregistrée.
|
||||
- history_menu: un fragment de HTML expliquant l'historique de la note de chaque étudiant modifié.
|
||||
|
||||
SAMPLES
|
||||
-------
|
||||
/evaluation/1/notes/set;{""notes"": [[1, 17], [2, ""SUPR""]], ""comment"" : ""sample test""}
|
||||
"""
|
||||
query = Evaluation.query.filter_by(id=evaluation_id)
|
||||
if g.scodoc_dept:
|
||||
@ -186,8 +192,9 @@ def evaluation_set_notes(evaluation_id: int): # evaluation-notes-set
|
||||
@as_json
|
||||
def evaluation_create(moduleimpl_id: int):
|
||||
"""Création d'une évaluation.
|
||||
The request content type should be "application/json",
|
||||
and contains:
|
||||
|
||||
DATA
|
||||
----
|
||||
{
|
||||
"description" : str,
|
||||
"evaluation_type" : int, // {0,1,2} default 0 (normale)
|
||||
@ -200,7 +207,13 @@ def evaluation_create(moduleimpl_id: int):
|
||||
"coefficient" : float, // si non spécifié, 1.0
|
||||
"poids" : { ue_id : poids } // optionnel
|
||||
}
|
||||
Result: l'évaluation créée.
|
||||
|
||||
Résultat: l'évaluation créée.
|
||||
|
||||
SAMPLES
|
||||
-------
|
||||
/moduleimpl/1/evaluation/create;{""description"":""Exemple éval.""}
|
||||
|
||||
"""
|
||||
moduleimpl: ModuleImpl = ModuleImpl.query.get_or_404(moduleimpl_id)
|
||||
if not moduleimpl.can_edit_evaluation(current_user):
|
||||
@ -250,7 +263,7 @@ def evaluation_create(moduleimpl_id: int):
|
||||
@as_json
|
||||
def evaluation_delete(evaluation_id: int):
|
||||
"""Suppression d'une évaluation.
|
||||
Efface aussi toutes ses notes
|
||||
Efface aussi toutes ses notes.
|
||||
"""
|
||||
query = Evaluation.query.filter_by(id=evaluation_id)
|
||||
if g.scodoc_dept:
|
||||
|
@ -6,6 +6,10 @@
|
||||
|
||||
"""
|
||||
ScoDoc 9 API : accès aux formations
|
||||
|
||||
CATEGORY
|
||||
--------
|
||||
Formations
|
||||
"""
|
||||
|
||||
from flask import flash, g, request
|
||||
@ -38,7 +42,13 @@ from app.scodoc.sco_permissions import Permission
|
||||
@as_json
|
||||
def formations():
|
||||
"""
|
||||
Retourne la liste de toutes les formations (tous départements)
|
||||
Retourne la liste de toutes les formations (tous départements,
|
||||
sauf si route départementale).
|
||||
|
||||
SAMPLES
|
||||
-------
|
||||
/formations;
|
||||
|
||||
"""
|
||||
query = Formation.query
|
||||
if g.scodoc_dept:
|
||||
@ -58,7 +68,12 @@ def formations_ids():
|
||||
Retourne la liste de toutes les id de formations
|
||||
(tous départements, ou du département indiqué dans la route)
|
||||
|
||||
Exemple de résultat : [ 17, 99, 32 ]
|
||||
Exemple de résultat : `[ 17, 99, 32 ]`.
|
||||
|
||||
SAMPLES
|
||||
-------
|
||||
/formations_ids;
|
||||
|
||||
"""
|
||||
query = Formation.query
|
||||
if g.scodoc_dept:
|
||||
@ -72,26 +87,14 @@ def formations_ids():
|
||||
@scodoc
|
||||
@permission_required(Permission.ScoView)
|
||||
@as_json
|
||||
def formation_by_id(formation_id: int):
|
||||
def formation_get(formation_id: int):
|
||||
"""
|
||||
La formation d'id donné
|
||||
La formation d'id donné.
|
||||
|
||||
formation_id : l'id d'une formation
|
||||
SAMPLES
|
||||
-------
|
||||
/formation/1;
|
||||
|
||||
Exemple de résultat :
|
||||
{
|
||||
"id": 1,
|
||||
"acronyme": "BUT R&T",
|
||||
"titre_officiel": "Bachelor technologique réseaux et télécommunications",
|
||||
"formation_code": "V1RET",
|
||||
"code_specialite": null,
|
||||
"dept_id": 1,
|
||||
"titre": "BUT R&T",
|
||||
"version": 1,
|
||||
"type_parcours": 700,
|
||||
"referentiel_competence_id": null,
|
||||
"formation_id": 1
|
||||
}
|
||||
"""
|
||||
query = Formation.query.filter_by(id=formation_id)
|
||||
if g.scodoc_dept:
|
||||
@ -123,97 +126,14 @@ def formation_export_by_formation_id(formation_id: int, export_ids=False):
|
||||
"""
|
||||
Retourne la formation, avec UE, matières, modules
|
||||
|
||||
PARAMS
|
||||
------
|
||||
formation_id : l'id d'une formation
|
||||
export_ids : True ou False, si l'on veut ou non exporter les ids
|
||||
export_with_ids : si présent, exporte aussi les ids des objets ScoDoc de la formation.
|
||||
|
||||
Exemple de résultat :
|
||||
{
|
||||
"id": 1,
|
||||
"acronyme": "BUT R&T",
|
||||
"titre_officiel": "Bachelor technologique r\u00e9seaux et t\u00e9l\u00e9communications",
|
||||
"formation_code": "V1RET",
|
||||
"code_specialite": null,
|
||||
"dept_id": 1,
|
||||
"titre": "BUT R&T",
|
||||
"version": 1,
|
||||
"type_parcours": 700,
|
||||
"referentiel_competence_id": null,
|
||||
"formation_id": 1,
|
||||
"ue": [
|
||||
{
|
||||
"acronyme": "RT1.1",
|
||||
"numero": 1,
|
||||
"titre": "Administrer les r\u00e9seaux et l\u2019Internet",
|
||||
"type": 0,
|
||||
"ue_code": "UCOD11",
|
||||
"ects": 12.0,
|
||||
"is_external": false,
|
||||
"code_apogee": "",
|
||||
"coefficient": 0.0,
|
||||
"semestre_idx": 1,
|
||||
"color": "#B80004",
|
||||
"reference": 1,
|
||||
"matiere": [
|
||||
{
|
||||
"titre": "Administrer les r\u00e9seaux et l\u2019Internet",
|
||||
"numero": 1,
|
||||
"module": [
|
||||
{
|
||||
"titre": "Initiation aux r\u00e9seaux informatiques",
|
||||
"abbrev": "Init aux r\u00e9seaux informatiques",
|
||||
"code": "R101",
|
||||
"heures_cours": 0.0,
|
||||
"heures_td": 0.0,
|
||||
"heures_tp": 0.0,
|
||||
"coefficient": 1.0,
|
||||
"ects": "",
|
||||
"semestre_id": 1,
|
||||
"numero": 10,
|
||||
"code_apogee": "",
|
||||
"module_type": 2,
|
||||
"coefficients": [
|
||||
{
|
||||
"ue_reference": "1",
|
||||
"coef": "12.0"
|
||||
},
|
||||
{
|
||||
"ue_reference": "2",
|
||||
"coef": "4.0"
|
||||
},
|
||||
{
|
||||
"ue_reference": "3",
|
||||
"coef": "4.0"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"titre": "Se sensibiliser \u00e0 l'hygi\u00e8ne informatique...",
|
||||
"abbrev": "Hygi\u00e8ne informatique",
|
||||
"code": "SAE11",
|
||||
"heures_cours": 0.0,
|
||||
"heures_td": 0.0,
|
||||
"heures_tp": 0.0,
|
||||
"coefficient": 1.0,
|
||||
"ects": "",
|
||||
"semestre_id": 1,
|
||||
"numero": 10,
|
||||
"code_apogee": "",
|
||||
"module_type": 3,
|
||||
"coefficients": [
|
||||
{
|
||||
"ue_reference": "1",
|
||||
"coef": "16.0"
|
||||
}
|
||||
]
|
||||
},
|
||||
...
|
||||
]
|
||||
},
|
||||
...
|
||||
]
|
||||
},
|
||||
]
|
||||
}
|
||||
SAMPLES
|
||||
-------
|
||||
/formation/1/export
|
||||
"""
|
||||
query = Formation.query.filter_by(id=formation_id)
|
||||
if g.scodoc_dept:
|
||||
@ -236,11 +156,13 @@ def formation_export_by_formation_id(formation_id: int, export_ids=False):
|
||||
@as_json
|
||||
def referentiel_competences(formation_id: int):
|
||||
"""
|
||||
Retourne le référentiel de compétences
|
||||
Retourne le référentiel de compétences de la formation
|
||||
ou null si pas de référentiel associé.
|
||||
|
||||
formation_id : l'id d'une formation
|
||||
SAMPLES
|
||||
-------
|
||||
/formation/1/referentiel_competences;
|
||||
|
||||
return null si pas de référentiel associé.
|
||||
"""
|
||||
query = Formation.query.filter_by(id=formation_id)
|
||||
if g.scodoc_dept:
|
||||
@ -259,8 +181,14 @@ def referentiel_competences(formation_id: int):
|
||||
@as_json
|
||||
def ue_set_parcours(ue_id: int):
|
||||
"""Associe UE et parcours BUT.
|
||||
|
||||
La liste des ids de parcours est passée en argument JSON.
|
||||
JSON arg: [parcour_id1, parcour_id2, ...]
|
||||
|
||||
DATA
|
||||
----
|
||||
```json
|
||||
[ parcour_id1, parcour_id2, ... ]
|
||||
```
|
||||
"""
|
||||
query = UniteEns.query.filter_by(id=ue_id)
|
||||
if g.scodoc_dept:
|
||||
@ -293,7 +221,7 @@ def ue_set_parcours(ue_id: int):
|
||||
@permission_required(Permission.EditFormation)
|
||||
@as_json
|
||||
def ue_assoc_niveau(ue_id: int, niveau_id: int):
|
||||
"""Associe l'UE au niveau de compétence"""
|
||||
"""Associe l'UE au niveau de compétence."""
|
||||
query = UniteEns.query.filter_by(id=ue_id)
|
||||
if g.scodoc_dept:
|
||||
query = query.join(Formation).filter_by(dept_id=g.scodoc_dept_id)
|
||||
@ -323,7 +251,7 @@ def ue_assoc_niveau(ue_id: int, niveau_id: int):
|
||||
@as_json
|
||||
def ue_desassoc_niveau(ue_id: int):
|
||||
"""Désassocie cette UE de son niveau de compétence
|
||||
(si elle n'est pas associée, ne fait rien)
|
||||
(si elle n'est pas associée, ne fait rien).
|
||||
"""
|
||||
query = UniteEns.query.filter_by(id=ue_id)
|
||||
if g.scodoc_dept:
|
||||
@ -345,7 +273,12 @@ def ue_desassoc_niveau(ue_id: int):
|
||||
@scodoc
|
||||
@permission_required(Permission.ScoView)
|
||||
def get_ue(ue_id: int):
|
||||
"""Renvoie l'UE"""
|
||||
"""Renvoie l'UE.
|
||||
|
||||
SAMPLES
|
||||
-------
|
||||
/formation/ue/1;
|
||||
"""
|
||||
query = UniteEns.query.filter_by(id=ue_id)
|
||||
if g.scodoc_dept:
|
||||
query = query.join(Formation).filter_by(dept_id=g.scodoc_dept_id)
|
||||
@ -359,7 +292,12 @@ def get_ue(ue_id: int):
|
||||
@scodoc
|
||||
@permission_required(Permission.ScoView)
|
||||
def formation_module_get(module_id: int):
|
||||
"""Renvoie le module"""
|
||||
"""Renvoie le module.
|
||||
|
||||
SAMPLES
|
||||
-------
|
||||
/formation/module/1;
|
||||
"""
|
||||
query = Module.query.filter_by(id=module_id)
|
||||
if g.scodoc_dept:
|
||||
query = query.join(Formation).filter_by(dept_id=g.scodoc_dept_id)
|
||||
@ -390,15 +328,17 @@ def formation_module_get(module_id: int):
|
||||
@permission_required(Permission.EditFormation)
|
||||
def ue_set_code_apogee(ue_id: int | None = None, code_apogee: str = ""):
|
||||
"""Change le code Apogée de l'UE.
|
||||
|
||||
Le code est une chaîne, avec éventuellement plusieurs valeurs séparées
|
||||
par des virgules.
|
||||
(Ce changement peut être fait sur formation verrouillée)
|
||||
|
||||
Si ue_id n'est pas spécifié, utilise l'argument oid du POST.
|
||||
Si code_apogee n'est pas spécifié ou vide,
|
||||
utilise l'argument value du POST
|
||||
Ce changement peut être fait sur formation verrouillée.
|
||||
|
||||
Le retour est une chaîne (le code enregistré), pas json.
|
||||
Si `ue_id` n'est pas spécifié, utilise l'argument oid du POST.
|
||||
Si `code_apogee` n'est pas spécifié ou vide,
|
||||
utilise l'argument value du POST.
|
||||
|
||||
Le retour est une chaîne (le code enregistré), pas du json.
|
||||
"""
|
||||
if ue_id is None:
|
||||
ue_id = request.form.get("oid")
|
||||
@ -444,14 +384,16 @@ def ue_set_code_apogee(ue_id: int | None = None, code_apogee: str = ""):
|
||||
@permission_required(Permission.EditFormation)
|
||||
def ue_set_code_apogee_rcue(ue_id: int, code_apogee: str = ""):
|
||||
"""Change le code Apogée du RCUE de l'UE.
|
||||
|
||||
Le code est une chaîne, avec éventuellement plusieurs valeurs séparées
|
||||
par des virgules.
|
||||
(Ce changement peut être fait sur formation verrouillée)
|
||||
|
||||
Ce changement peut être fait sur formation verrouillée.
|
||||
|
||||
Si code_apogee n'est pas spécifié ou vide,
|
||||
utilise l'argument value du POST (utilisé par jinplace.js)
|
||||
utilise l'argument value du POST (utilisé par `jinplace.js`)
|
||||
|
||||
Le retour est une chaîne (le code enregistré), pas json.
|
||||
Le retour est une chaîne (le code enregistré), pas du json.
|
||||
"""
|
||||
if not code_apogee:
|
||||
code_apogee = request.form.get("value", "")
|
||||
@ -497,15 +439,17 @@ def formation_module_set_code_apogee(
|
||||
module_id: int | None = None, code_apogee: str = ""
|
||||
):
|
||||
"""Change le code Apogée du module.
|
||||
|
||||
Le code est une chaîne, avec éventuellement plusieurs valeurs séparées
|
||||
par des virgules.
|
||||
(Ce changement peut être fait sur formation verrouillée)
|
||||
|
||||
Si module_id n'est pas spécifié, utilise l'argument oid du POST.
|
||||
Si code_apogee n'est pas spécifié ou vide,
|
||||
Ce changement peut être fait sur formation verrouillée.
|
||||
|
||||
Si `module_id` n'est pas spécifié, utilise l'argument `oid` du POST.
|
||||
Si `code_apogee` n'est pas spécifié ou vide,
|
||||
utilise l'argument value du POST (utilisé par jinplace.js)
|
||||
|
||||
Le retour est une chaîne (le code enregistré), pas json.
|
||||
Le retour est une chaîne (le code enregistré), pas du json.
|
||||
"""
|
||||
if module_id is None:
|
||||
module_id = request.form.get("oid")
|
||||
|
@ -6,6 +6,12 @@
|
||||
|
||||
"""
|
||||
ScoDoc 9 API : accès aux formsemestres
|
||||
|
||||
|
||||
CATEGORY
|
||||
--------
|
||||
FormSemestre
|
||||
|
||||
"""
|
||||
from operator import attrgetter, itemgetter
|
||||
|
||||
@ -48,43 +54,15 @@ from app.tables.recap import TableRecap, RowRecap
|
||||
@scodoc
|
||||
@permission_required(Permission.ScoView)
|
||||
@as_json
|
||||
def formsemestre_infos(formsemestre_id: int):
|
||||
def formsemestre_get(formsemestre_id: int):
|
||||
"""
|
||||
Information sur le formsemestre indiqué.
|
||||
|
||||
formsemestre_id : l'id du formsemestre
|
||||
|
||||
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",
|
||||
"dept_id": 1,
|
||||
"elt_annee_apo": null,
|
||||
"elt_passage_apo" : null,
|
||||
"elt_sem_apo": null,
|
||||
"ens_can_edit_eval": false,
|
||||
"etat": true,
|
||||
"formation_id": 1,
|
||||
"formsemestre_id": 1,
|
||||
"gestion_compensation": false,
|
||||
"gestion_semestrielle": false,
|
||||
"id": 1,
|
||||
"modalite": "FI",
|
||||
"resp_can_change_ens": true,
|
||||
"resp_can_edit": false,
|
||||
"responsables": [1, 99], // uids
|
||||
"scodoc7_id": null,
|
||||
"semestre_id": 1,
|
||||
"titre_formation" : "BUT GEA",
|
||||
"titre_num": "BUT GEA semestre 1",
|
||||
"titre": "BUT GEA",
|
||||
}
|
||||
|
||||
SAMPLES
|
||||
-------
|
||||
/formsemestre/1
|
||||
"""
|
||||
query = FormSemestre.query.filter_by(id=formsemestre_id)
|
||||
if g.scodoc_dept:
|
||||
@ -101,9 +79,11 @@ def formsemestre_infos(formsemestre_id: int):
|
||||
@as_json
|
||||
def formsemestres_query():
|
||||
"""
|
||||
Retourne les formsemestres filtrés par
|
||||
étape Apogée ou année scolaire ou département (acronyme ou id) ou état ou code étudiant
|
||||
Retourne les formsemestres filtrés par étape Apogée ou année scolaire
|
||||
ou département (acronyme ou id) ou état ou code étudiant.
|
||||
|
||||
PARAMS
|
||||
------
|
||||
etape_apo : un code étape apogée
|
||||
annee_scolaire : année de début de l'année scolaire
|
||||
dept_acronym : acronyme du département (eg "RT")
|
||||
@ -190,7 +170,36 @@ def formsemestres_query():
|
||||
@permission_required(Permission.EditFormSemestre)
|
||||
@as_json
|
||||
def formsemestre_edit(formsemestre_id: int):
|
||||
"""Modifie les champs d'un formsemestre."""
|
||||
"""Modifie les champs d'un formsemestre.
|
||||
|
||||
On peut spécifier un ou plusieurs champs.
|
||||
|
||||
DATA
|
||||
---
|
||||
```json
|
||||
{
|
||||
"semestre_id" : string,
|
||||
"titre" : string,
|
||||
"date_debut" : date iso,
|
||||
"date_fin" : date iso,
|
||||
"edt_id" : string,
|
||||
"etat" : string,
|
||||
"modalite" : string,
|
||||
"gestion_compensation" : bool,
|
||||
"bul_hide_xml" : bool,
|
||||
"block_moyennes" : bool,
|
||||
"block_moyenne_generale" : bool,
|
||||
"mode_calcul_moyennes" : string,
|
||||
"gestion_semestrielle" : string,
|
||||
"bul_bgcolor" : string,
|
||||
"resp_can_edit" : bool,
|
||||
"resp_can_change_ens" : bool,
|
||||
"ens_can_edit_eval" : bool,
|
||||
"elt_sem_apo" : string,
|
||||
"elt_annee_apo : string,
|
||||
}
|
||||
```
|
||||
"""
|
||||
formsemestre = FormSemestre.get_formsemestre(formsemestre_id)
|
||||
args = request.get_json(force=True) # may raise 400 Bad Request
|
||||
editable_keys = {
|
||||
@ -228,13 +237,19 @@ def formsemestre_edit(formsemestre_id: int):
|
||||
@permission_required(Permission.EditApogee)
|
||||
def formsemestre_set_apo_etapes():
|
||||
"""Change les codes étapes du semestre indiqué.
|
||||
|
||||
Le code est une chaîne, avec éventuellement plusieurs valeurs séparées
|
||||
par des virgules.
|
||||
(Ce changement peut être fait sur un semestre verrouillé)
|
||||
|
||||
Args:
|
||||
oid=int, le formsemestre_id
|
||||
value=chaine "V1RT, V1RT2", codes séparés par des virgules
|
||||
Ce changement peut être fait sur un semestre verrouillé.
|
||||
|
||||
DATA
|
||||
----
|
||||
```json
|
||||
{
|
||||
oid : int, le formsemestre_id
|
||||
value : string, eg "V1RT, V1RT2", codes séparés par des virgules
|
||||
}
|
||||
"""
|
||||
formsemestre_id = int(request.form.get("oid"))
|
||||
etapes_apo_str = request.form.get("value")
|
||||
@ -265,13 +280,20 @@ def formsemestre_set_apo_etapes():
|
||||
@permission_required(Permission.EditApogee)
|
||||
def formsemestre_set_elt_sem_apo():
|
||||
"""Change les codes étapes du semestre indiqué.
|
||||
|
||||
Le code est une chaîne, avec éventuellement plusieurs valeurs séparées
|
||||
par des virgules.
|
||||
(Ce changement peut être fait sur un semestre verrouillé)
|
||||
|
||||
Args:
|
||||
oid=int, le formsemestre_id
|
||||
value=chaine "V3ONM, V3ONM1, V3ONM2", codes séparés par des virgules
|
||||
Ce changement peut être fait sur un semestre verrouillé.
|
||||
|
||||
DATA
|
||||
----
|
||||
```json
|
||||
{
|
||||
oid : int, le formsemestre_id
|
||||
value : string, eg "V3ONM, V3ONM1, V3ONM2", codes séparés par des virgules
|
||||
}
|
||||
```
|
||||
"""
|
||||
oid = int(request.form.get("oid"))
|
||||
value = (request.form.get("value") or "").strip()
|
||||
@ -293,13 +315,20 @@ def formsemestre_set_elt_sem_apo():
|
||||
@permission_required(Permission.EditApogee)
|
||||
def formsemestre_set_elt_annee_apo():
|
||||
"""Change les codes étapes du semestre indiqué (par le champ oid).
|
||||
|
||||
Le code est une chaîne, avec éventuellement plusieurs valeurs séparées
|
||||
par des virgules.
|
||||
(Ce changement peut être fait sur un semestre verrouillé)
|
||||
|
||||
Args:
|
||||
oid=int, le formsemestre_id
|
||||
value=chaine "V3ONM, V3ONM1, V3ONM2", codes séparés par des virgules
|
||||
Ce changement peut être fait sur un semestre verrouillé.
|
||||
|
||||
DATA
|
||||
----
|
||||
```json
|
||||
{
|
||||
oid : int, le formsemestre_id
|
||||
value : string, eg "V3ONM, V3ONM1, V3ONM2", codes séparés par des virgules
|
||||
}
|
||||
```
|
||||
"""
|
||||
oid = int(request.form.get("oid"))
|
||||
value = (request.form.get("value") or "").strip()
|
||||
@ -320,14 +349,21 @@ def formsemestre_set_elt_annee_apo():
|
||||
@scodoc
|
||||
@permission_required(Permission.EditApogee)
|
||||
def formsemestre_set_elt_passage_apo():
|
||||
"""Change les codes apogée de passage du semestre indiqué (par le champ oid).
|
||||
"""Change les codes Apogée de passage du semestre indiqué (par le champ oid).
|
||||
|
||||
Le code est une chaîne, avec éventuellement plusieurs valeurs séparées
|
||||
par des virgules.
|
||||
(Ce changement peut être fait sur un semestre verrouillé)
|
||||
|
||||
Args:
|
||||
oid=int, le formsemestre_id
|
||||
value=chaine "V3ONM, V3ONM1, V3ONM2", codes séparés par des virgules
|
||||
Ce changement peut être fait sur un semestre verrouillé.
|
||||
|
||||
DATA
|
||||
----
|
||||
```json
|
||||
{
|
||||
oid : int, le formsemestre_id
|
||||
value : string, eg "V3ONM, V3ONM1, V3ONM2", codes séparés par des virgules
|
||||
}
|
||||
```
|
||||
"""
|
||||
oid = int(request.form.get("oid"))
|
||||
value = (request.form.get("value") or "").strip()
|
||||
@ -353,11 +389,16 @@ def formsemestre_set_elt_passage_apo():
|
||||
@as_json
|
||||
def bulletins(formsemestre_id: int, version: str = "long"):
|
||||
"""
|
||||
Retourne les bulletins d'un formsemestre donné
|
||||
Retourne les bulletins d'un formsemestre.
|
||||
|
||||
formsemestre_id : l'id d'un formesemestre
|
||||
PARAMS
|
||||
------
|
||||
formsemestre_id : int
|
||||
version : string ("long", "short", "selectedevals")
|
||||
|
||||
Exemple de résultat : liste, voir https://scodoc.org/ScoDoc9API/#bulletin
|
||||
SAMPLES
|
||||
-------
|
||||
/formsemestre/1/bulletins
|
||||
"""
|
||||
query = FormSemestre.query.filter_by(id=formsemestre_id)
|
||||
if g.scodoc_dept:
|
||||
@ -387,66 +428,9 @@ def formsemestre_programme(formsemestre_id: int):
|
||||
"""
|
||||
Retourne la liste des UEs, ressources et SAEs d'un semestre
|
||||
|
||||
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": [
|
||||
{
|
||||
"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": [
|
||||
{
|
||||
...
|
||||
},
|
||||
...
|
||||
],
|
||||
"modules" : [ ... les modules qui ne sont ni des SAEs ni des ressources ... ]
|
||||
}
|
||||
SAMPLES
|
||||
-------
|
||||
/formsemestre/1/programme
|
||||
"""
|
||||
query = FormSemestre.query.filter_by(id=formsemestre_id)
|
||||
if g.scodoc_dept:
|
||||
@ -519,6 +503,10 @@ def formsemestre_etudiants(
|
||||
-----
|
||||
etat:<string:etat>
|
||||
|
||||
SAMPLES
|
||||
-------
|
||||
/formsemestre/1/etudiants/query;
|
||||
|
||||
"""
|
||||
query = FormSemestre.query.filter_by(id=formsemestre_id)
|
||||
if g.scodoc_dept:
|
||||
@ -565,36 +553,9 @@ def formsemestre_etat_evaluations(formsemestre_id: int):
|
||||
"""
|
||||
Informations sur l'état des évaluations d'un formsemestre.
|
||||
|
||||
formsemestre_id : l'id d'un semestre
|
||||
|
||||
Exemple de résultat :
|
||||
[
|
||||
{
|
||||
"id": 1, // moduleimpl_id
|
||||
"titre": "Initiation aux réseaux informatiques",
|
||||
"evaluations": [
|
||||
{
|
||||
"id": 1,
|
||||
"description": null,
|
||||
"datetime_epreuve": null,
|
||||
"heure_fin": "09:00:00",
|
||||
"coefficient": "02.00"
|
||||
"is_complete": true,
|
||||
"nb_inscrits": 16,
|
||||
"nb_manquantes": 0,
|
||||
"ABS": 0,
|
||||
"ATT": 0,
|
||||
"EXC": 0,
|
||||
"saisie_notes": {
|
||||
"datetime_debut": "2021-09-11T00:00:00+02:00",
|
||||
"datetime_fin": "2022-08-25T00:00:00+02:00",
|
||||
"datetime_mediane": "2022-03-19T00:00:00+01:00"
|
||||
}
|
||||
},
|
||||
...
|
||||
]
|
||||
},
|
||||
]
|
||||
SAMPLES
|
||||
-------
|
||||
/formsemestre/1/etat_evals
|
||||
"""
|
||||
formsemestre = FormSemestre.get_formsemestre(formsemestre_id)
|
||||
app.set_sco_dept(formsemestre.departement.acronym)
|
||||
@ -669,7 +630,8 @@ def formsemestre_etat_evaluations(formsemestre_id: int):
|
||||
@permission_required(Permission.ScoView)
|
||||
@as_json
|
||||
def formsemestre_resultat(formsemestre_id: int):
|
||||
"""Tableau récapitulatif des résultats
|
||||
"""Tableau récapitulatif des résultats.
|
||||
|
||||
Pour chaque étudiant, son état, ses groupes, ses moyennes d'UE et de modules.
|
||||
|
||||
Si `format=raw`, ne converti pas les valeurs.
|
||||
@ -678,6 +640,9 @@ def formsemestre_resultat(formsemestre_id: int):
|
||||
-----
|
||||
format:<string:format>
|
||||
|
||||
SAMPLES
|
||||
-------
|
||||
/formsemestre/1/resultats;
|
||||
"""
|
||||
format_spec = request.args.get("format", None)
|
||||
if format_spec is not None and format_spec != "raw":
|
||||
@ -724,7 +689,7 @@ def formsemestre_resultat(formsemestre_id: int):
|
||||
@permission_required(Permission.ScoView)
|
||||
@as_json
|
||||
def groups_get_auto_assignment(formsemestre_id: int):
|
||||
"""rend les données stockées par"""
|
||||
"""Rend les données stockées par `groups_save_auto_assignment`."""
|
||||
query = FormSemestre.query.filter_by(id=formsemestre_id)
|
||||
if g.scodoc_dept:
|
||||
query = query.filter_by(dept_id=g.scodoc_dept_id)
|
||||
@ -745,12 +710,17 @@ def groups_get_auto_assignment(formsemestre_id: int):
|
||||
@permission_required(Permission.ScoView)
|
||||
@as_json
|
||||
def groups_save_auto_assignment(formsemestre_id: int):
|
||||
"""enregistre les données"""
|
||||
"""Enregistre les données, associées à ce formsemestre.
|
||||
Usage réservé aux fonctions de gestion des groupes, ne pas utiliser ailleurs.
|
||||
"""
|
||||
query = FormSemestre.query.filter_by(id=formsemestre_id)
|
||||
if g.scodoc_dept:
|
||||
query = query.filter_by(dept_id=g.scodoc_dept_id)
|
||||
formsemestre: FormSemestre = query.first_or_404(formsemestre_id)
|
||||
|
||||
if not formsemestre.can_change_groups():
|
||||
return json_error(403, "non autorisé (can_change_groups)")
|
||||
|
||||
if len(request.data) > GROUPS_AUTO_ASSIGNMENT_DATA_MAX:
|
||||
return json_error(413, "data too large")
|
||||
formsemestre.groups_auto_assignment_data = request.data
|
||||
@ -765,17 +735,16 @@ def groups_save_auto_assignment(formsemestre_id: int):
|
||||
@permission_required(Permission.ScoView)
|
||||
@as_json
|
||||
def formsemestre_edt(formsemestre_id: int):
|
||||
"""l'emploi du temps du semestre.
|
||||
"""L'emploi du temps du semestre.
|
||||
|
||||
Si ok, une liste d'évènements. Sinon, une chaine indiquant un message d'erreur.
|
||||
|
||||
group_ids permet de filtrer sur les groupes ScoDoc.
|
||||
show_modules_titles affiche le titre complet du module (défaut), sinon juste le code.
|
||||
Expérimental, ne pas utiliser hors ScoDoc.
|
||||
|
||||
QUERY
|
||||
-----
|
||||
group_ids:<string:group_ids>
|
||||
show_modules_titles:<bool:show_modules_titles>
|
||||
|
||||
group_ids : string (optionnel) filtre sur les groupes ScoDoc.
|
||||
show_modules_titles: show_modules_titles affiche le titre complet du module (défaut), sinon juste le code.
|
||||
"""
|
||||
query = FormSemestre.query.filter_by(id=formsemestre_id)
|
||||
if g.scodoc_dept:
|
||||
|
@ -5,7 +5,12 @@
|
||||
##############################################################################
|
||||
|
||||
"""
|
||||
ScoDoc 9 API : jury WIP à compléter avec enregistrement décisions
|
||||
ScoDoc 9 API : jury WIP à compléter avec enregistrement décisions.
|
||||
|
||||
|
||||
CATEGORY
|
||||
--------
|
||||
Jury
|
||||
"""
|
||||
|
||||
import datetime
|
||||
@ -48,7 +53,12 @@ from app.scodoc.sco_utils import json_error
|
||||
@permission_required(Permission.ScoView)
|
||||
@as_json
|
||||
def decisions_jury(formsemestre_id: int):
|
||||
"""Décisions du jury des étudiants du formsemestre."""
|
||||
"""Décisions du jury des étudiants du formsemestre.
|
||||
|
||||
SAMPLES
|
||||
-------
|
||||
/formsemestre/1/decisions_jury
|
||||
"""
|
||||
# APC, pair:
|
||||
formsemestre: FormSemestre = db.session.get(FormSemestre, formsemestre_id)
|
||||
if formsemestre is None:
|
||||
@ -91,7 +101,7 @@ def _news_delete_jury_etud(etud: Identite, detail: str = ""):
|
||||
@permission_required(Permission.ScoView)
|
||||
@as_json
|
||||
def validation_ue_delete(etudid: int, validation_id: int):
|
||||
"Efface cette validation"
|
||||
"Efface cette validation d'UE."
|
||||
return _validation_ue_delete(etudid, validation_id)
|
||||
|
||||
|
||||
@ -108,7 +118,7 @@ def validation_ue_delete(etudid: int, validation_id: int):
|
||||
@permission_required(Permission.ScoView)
|
||||
@as_json
|
||||
def validation_formsemestre_delete(etudid: int, validation_id: int):
|
||||
"Efface cette validation"
|
||||
"Efface cette validation de semestre."
|
||||
# c'est la même chose (formations classiques)
|
||||
return _validation_ue_delete(etudid, validation_id)
|
||||
|
||||
@ -160,7 +170,7 @@ def _validation_ue_delete(etudid: int, validation_id: int):
|
||||
@permission_required(Permission.EtudInscrit)
|
||||
@as_json
|
||||
def autorisation_inscription_delete(etudid: int, validation_id: int):
|
||||
"Efface cette validation"
|
||||
"Efface cette autorisation d'inscription."
|
||||
etud = tools.get_etud(etudid)
|
||||
if etud is None:
|
||||
return "étudiant inconnu", 404
|
||||
@ -189,8 +199,12 @@ def autorisation_inscription_delete(etudid: int, validation_id: int):
|
||||
@as_json
|
||||
def validation_rcue_record(etudid: int):
|
||||
"""Enregistre une validation de RCUE.
|
||||
|
||||
Si une validation existe déjà pour ce RCUE, la remplace.
|
||||
The request content type should be "application/json":
|
||||
|
||||
DATA
|
||||
----
|
||||
```json
|
||||
{
|
||||
"code" : str,
|
||||
"ue1_id" : int,
|
||||
@ -200,6 +214,7 @@ def validation_rcue_record(etudid: int):
|
||||
"date" : date_iso, // si non spécifié, now()
|
||||
"parcours_id" :int,
|
||||
}
|
||||
```
|
||||
"""
|
||||
etud = tools.get_etud(etudid)
|
||||
if etud is None:
|
||||
@ -314,7 +329,7 @@ def validation_rcue_record(etudid: int):
|
||||
@permission_required(Permission.EtudInscrit)
|
||||
@as_json
|
||||
def validation_rcue_delete(etudid: int, validation_id: int):
|
||||
"Efface cette validation"
|
||||
"Efface cette validation de RCUE."
|
||||
etud = tools.get_etud(etudid)
|
||||
if etud is None:
|
||||
return "étudiant inconnu", 404
|
||||
@ -342,7 +357,7 @@ def validation_rcue_delete(etudid: int, validation_id: int):
|
||||
@permission_required(Permission.EtudInscrit)
|
||||
@as_json
|
||||
def validation_annee_but_delete(etudid: int, validation_id: int):
|
||||
"Efface cette validation"
|
||||
"Efface cette validation d'année BUT."
|
||||
etud = tools.get_etud(etudid)
|
||||
if etud is None:
|
||||
return "étudiant inconnu", 404
|
||||
@ -371,7 +386,7 @@ def validation_annee_but_delete(etudid: int, validation_id: int):
|
||||
@permission_required(Permission.EtudInscrit)
|
||||
@as_json
|
||||
def validation_dut120_delete(etudid: int, validation_id: int):
|
||||
"Efface cette validation"
|
||||
"Efface cette validation de DUT120."
|
||||
etud = tools.get_etud(etudid)
|
||||
if etud is None:
|
||||
return "étudiant inconnu", 404
|
||||
|
@ -38,9 +38,11 @@ from app.scodoc.sco_groups import get_group_members
|
||||
@scodoc
|
||||
@permission_required(Permission.ScoView)
|
||||
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:
|
||||
|
||||
```json
|
||||
{
|
||||
"justif_id": 1,
|
||||
"etudid": 2,
|
||||
@ -52,6 +54,11 @@ def justificatif(justif_id: int = None):
|
||||
"entry_date": "2022-10-31T08:00+01:00",
|
||||
"user_id": 1 or null,
|
||||
}
|
||||
```
|
||||
|
||||
SAMPLES
|
||||
-------
|
||||
/justificatif/1;
|
||||
|
||||
"""
|
||||
|
||||
@ -92,38 +99,32 @@ 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/<int:etudid>
|
||||
|
||||
Un filtrage peut être donné avec une query
|
||||
chemin : /justificatifs/<int:etudid>/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:<int:user_id>
|
||||
est_just:<bool:est_just>
|
||||
date_debut:<string:date_debut_iso>
|
||||
date_fin:<string:date_fin_iso>
|
||||
etat:<array[string]:etat>
|
||||
order:<bool:order>
|
||||
courant:<bool:courant>
|
||||
group_id:<int: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:<int:group_id>
|
||||
|
||||
SAMPLES
|
||||
-------
|
||||
/justificatifs/1;
|
||||
/justificatifs/1/query?etat=attente;
|
||||
|
||||
"""
|
||||
# Récupération de l'étudiant
|
||||
etud: Identite = tools.get_etud(etudid, nip, ine)
|
||||
@ -164,7 +165,7 @@ def justificatifs(etudid: int = None, nip=None, ine=None, with_query: bool = Fal
|
||||
def justificatifs_dept(dept_id: int = None, with_query: bool = False):
|
||||
"""
|
||||
Renvoie tous les justificatifs d'un département
|
||||
(en ajoutant un champ "formsemestre" si possible)
|
||||
(en ajoutant un champ "`formsemestre`" si possible).
|
||||
|
||||
QUERY
|
||||
-----
|
||||
@ -176,6 +177,21 @@ def justificatifs_dept(dept_id: int = None, with_query: bool = False):
|
||||
order:<bool:order>
|
||||
courant:<bool:courant>
|
||||
group_id:<int: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:<int:group_id>
|
||||
|
||||
SAMPLES
|
||||
-------
|
||||
/justificatifs/dept/1;
|
||||
|
||||
"""
|
||||
|
||||
# Récupération du département et des étudiants du département
|
||||
@ -204,9 +220,9 @@ def justificatifs_dept(dept_id: int = None, with_query: bool = False):
|
||||
|
||||
def _set_sems(justi: Justificatif, restrict: bool) -> dict:
|
||||
"""
|
||||
_set_sems Ajoute le formsemestre associé au justificatif s'il existe
|
||||
_set_sems Ajoute le formsemestre associé au justificatif s'il existe.
|
||||
|
||||
Si le formsemestre n'existe pas, renvoie la simple représentation du justificatif
|
||||
Si le formsemestre n'existe pas, renvoie la simple représentation du justificatif.
|
||||
|
||||
Args:
|
||||
justi (Justificatif): Le justificatif
|
||||
@ -247,7 +263,7 @@ def _set_sems(justi: Justificatif, restrict: bool) -> dict:
|
||||
@as_json
|
||||
@permission_required(Permission.ScoView)
|
||||
def justificatifs_formsemestre(formsemestre_id: int, with_query: bool = False):
|
||||
"""Retourne tous les justificatifs du formsemestre
|
||||
"""Retourne tous les justificatifs du formsemestre.
|
||||
|
||||
QUERY
|
||||
-----
|
||||
@ -259,6 +275,21 @@ def justificatifs_formsemestre(formsemestre_id: int, with_query: bool = False):
|
||||
order:<bool:order>
|
||||
courant:<bool:courant>
|
||||
group_id:<int: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:<int:group_id>
|
||||
|
||||
SAMPLES
|
||||
-------
|
||||
/justificatifs/formsemestre/1;
|
||||
|
||||
"""
|
||||
|
||||
# Récupération du formsemestre
|
||||
@ -306,8 +337,11 @@ def justificatifs_formsemestre(formsemestre_id: int, with_query: bool = False):
|
||||
@permission_required(Permission.AbsChange)
|
||||
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":
|
||||
Création d'un justificatif pour l'étudiant.
|
||||
|
||||
DATA
|
||||
----
|
||||
```json
|
||||
[
|
||||
{
|
||||
"date_debut": str,
|
||||
@ -322,6 +356,10 @@ 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""}]
|
||||
|
||||
"""
|
||||
|
||||
@ -451,15 +489,24 @@ def _create_one(
|
||||
@permission_required(Permission.AbsChange)
|
||||
def justif_edit(justif_id: int):
|
||||
"""
|
||||
Edition d'un justificatif à partir de son id
|
||||
La requête doit avoir un content type "application/json":
|
||||
Édition d'un justificatif à partir de son id.
|
||||
|
||||
DATA
|
||||
----
|
||||
```json
|
||||
{
|
||||
"etat"?: str,
|
||||
"raison"?: str
|
||||
"date_debut"?: str
|
||||
"date_fin"?: str
|
||||
}
|
||||
```
|
||||
|
||||
SAMPLES
|
||||
-------
|
||||
/justificatif/1/edit;{""etat"":""valide""}
|
||||
/justificatif/1/edit;{""raison"":""MEDIC""}
|
||||
|
||||
"""
|
||||
|
||||
# Récupération du justificatif à modifier
|
||||
@ -564,15 +611,20 @@ def justif_edit(justif_id: int):
|
||||
@permission_required(Permission.AbsChange)
|
||||
def justif_delete():
|
||||
"""
|
||||
Suppression d'un justificatif à partir de son id
|
||||
|
||||
Forme des données envoyées :
|
||||
Suppression d'un justificatif à partir de son id.
|
||||
|
||||
DATA
|
||||
----
|
||||
```json
|
||||
[
|
||||
<justif_id:int>,
|
||||
...
|
||||
]
|
||||
```
|
||||
|
||||
SAMPLES
|
||||
-------
|
||||
/justificatif/delete;[2, 2, 3]
|
||||
|
||||
"""
|
||||
|
||||
@ -647,7 +699,9 @@ def _delete_one(justif_id: int) -> tuple[int, str]:
|
||||
@permission_required(Permission.AbsChange)
|
||||
def justif_import(justif_id: int = None):
|
||||
"""
|
||||
Importation d'un fichier (création d'archive)
|
||||
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é
|
||||
@ -698,7 +752,10 @@ def justif_import(justif_id: int = None):
|
||||
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)
|
||||
|
||||
La permission est `ScoView` + (`AbsJustifView` ou être l'auteur du justificatif).
|
||||
|
||||
> 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)
|
||||
@ -735,15 +792,21 @@ def justif_export(justif_id: int | None = None, filename: str | None = None):
|
||||
@permission_required(Permission.AbsChange)
|
||||
def justif_remove(justif_id: int = None):
|
||||
"""
|
||||
Supression d'un fichier ou d'une archive
|
||||
{
|
||||
"remove": <"all"/"list">
|
||||
Supression d'un fichier ou d'une archive.
|
||||
|
||||
> Procédure de suppression de fichier : [supprimer un justificatif](FichiersJustificatifs.md#supprimer-un-fichier)
|
||||
|
||||
DATA
|
||||
----
|
||||
```json
|
||||
{
|
||||
"remove": <"all"/"list">,
|
||||
"filenames"?: [
|
||||
<filename:str>,
|
||||
...
|
||||
]
|
||||
}
|
||||
```
|
||||
"""
|
||||
|
||||
# On récupère le dictionnaire
|
||||
@ -808,7 +871,12 @@ def justif_remove(justif_id: int = None):
|
||||
@permission_required(Permission.ScoView)
|
||||
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é
|
||||
@ -850,7 +918,12 @@ def justif_list(justif_id: int = None):
|
||||
@permission_required(Permission.AbsChange)
|
||||
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é
|
||||
|
@ -50,7 +50,12 @@ from app.scodoc.sco_utils import json_error
|
||||
@permission_required(Permission.ScoSuperAdmin)
|
||||
@as_json
|
||||
def logo_list_globals():
|
||||
"""Liste tous les logos"""
|
||||
"""Liste des noms des logos définis pour le site ScoDoc.
|
||||
|
||||
SAMPLES
|
||||
-------
|
||||
/logos
|
||||
"""
|
||||
logos = list_logos()[None]
|
||||
return list(logos.keys())
|
||||
|
||||
@ -59,6 +64,15 @@ def logo_list_globals():
|
||||
@scodoc
|
||||
@permission_required(Permission.ScoSuperAdmin)
|
||||
def logo_get_global(logoname):
|
||||
"""Renvoie le logo global de nom donné.
|
||||
|
||||
L'image est au format png ou jpg; le format retourné dépend du format sous lequel
|
||||
l'image a été initialement enregistrée.
|
||||
|
||||
SAMPLES
|
||||
-------
|
||||
/logo/B
|
||||
"""
|
||||
logo = find_logo(logoname=logoname)
|
||||
if logo is None:
|
||||
return json_error(404, message="logo not found")
|
||||
@ -75,12 +89,19 @@ def _core_get_logos(dept_id) -> list:
|
||||
return list(logos.keys())
|
||||
|
||||
|
||||
@bp.route("/departement/<string:departement>/logos")
|
||||
@bp.route("/departement/<string:dept_acronym>/logos")
|
||||
@scodoc
|
||||
@permission_required(Permission.ScoSuperAdmin)
|
||||
@as_json
|
||||
def logo_get_local_by_acronym(departement):
|
||||
dept_id = Departement.from_acronym(departement).id
|
||||
def departement_logos(dept_acronym: str):
|
||||
"""Liste des noms des logos définis pour le département
|
||||
désigné par son acronyme.
|
||||
|
||||
SAMPLES
|
||||
-------
|
||||
/departement/TAPI/logos
|
||||
"""
|
||||
dept_id = Departement.from_acronym(dept_acronym).id
|
||||
return _core_get_logos(dept_id)
|
||||
|
||||
|
||||
@ -88,7 +109,10 @@ def logo_get_local_by_acronym(departement):
|
||||
@scodoc
|
||||
@permission_required(Permission.ScoSuperAdmin)
|
||||
@as_json
|
||||
def logo_get_local_by_id(dept_id):
|
||||
def departement_logos_by_id(dept_id):
|
||||
"""Liste des noms des logos définis pour le département
|
||||
désigné par son id.
|
||||
"""
|
||||
return _core_get_logos(dept_id)
|
||||
|
||||
|
||||
@ -108,6 +132,12 @@ def _core_get_logo(dept_id, logoname) -> Response:
|
||||
@scodoc
|
||||
@permission_required(Permission.ScoSuperAdmin)
|
||||
def logo_get_local_dept_by_acronym(departement, logoname):
|
||||
"""Le logo: image (format png ou jpg).
|
||||
|
||||
**Exemple d'utilisation:**
|
||||
|
||||
* `/ScoDoc/api/departement/MMI/logo/header`
|
||||
"""
|
||||
dept_id = Departement.from_acronym(departement).id
|
||||
return _core_get_logo(dept_id, logoname)
|
||||
|
||||
@ -116,4 +146,10 @@ def logo_get_local_dept_by_acronym(departement, logoname):
|
||||
@scodoc
|
||||
@permission_required(Permission.ScoSuperAdmin)
|
||||
def logo_get_local_dept_by_id(dept_id, logoname):
|
||||
"""Le logo: image (format png ou jpg).
|
||||
|
||||
**Exemple d'utilisation:**
|
||||
|
||||
* `/ScoDoc/api/departement/id/3/logo/header`
|
||||
"""
|
||||
return _core_get_logo(dept_id, logoname)
|
||||
|
@ -6,6 +6,10 @@
|
||||
|
||||
"""
|
||||
ScoDoc 9 API : accès aux moduleimpl
|
||||
|
||||
CATEGORY
|
||||
--------
|
||||
ModuleImpl
|
||||
"""
|
||||
|
||||
from flask_json import as_json
|
||||
@ -28,38 +32,15 @@ from app.scodoc.sco_permissions import Permission
|
||||
@as_json
|
||||
def moduleimpl(moduleimpl_id: int):
|
||||
"""
|
||||
Retourne un moduleimpl en fonction de son id
|
||||
Retourne le moduleimpl.
|
||||
|
||||
PARAMS
|
||||
------
|
||||
moduleimpl_id : l'id d'un moduleimpl
|
||||
|
||||
Exemple de résultat :
|
||||
{
|
||||
"id": 1,
|
||||
"formsemestre_id": 1,
|
||||
"module_id": 1,
|
||||
"responsable_id": 2,
|
||||
"moduleimpl_id": 1,
|
||||
"ens": [],
|
||||
"module": {
|
||||
"heures_tp": 0,
|
||||
"code_apogee": "",
|
||||
"titre": "Initiation aux réseaux informatiques",
|
||||
"coefficient": 1,
|
||||
"module_type": 2,
|
||||
"id": 1,
|
||||
"ects": null,
|
||||
"abbrev": "Init aux réseaux informatiques",
|
||||
"ue_id": 1,
|
||||
"code": "R101",
|
||||
"formation_id": 1,
|
||||
"heures_cours": 0,
|
||||
"matiere_id": 1,
|
||||
"heures_td": 0,
|
||||
"semestre_id": 1,
|
||||
"numero": 10,
|
||||
"module_id": 1
|
||||
}
|
||||
}
|
||||
SAMPLES
|
||||
-------
|
||||
/moduleimpl/1
|
||||
"""
|
||||
modimpl = ModuleImpl.get_modimpl(moduleimpl_id)
|
||||
return modimpl.to_dict(convert_objects=True)
|
||||
@ -72,16 +53,11 @@ def moduleimpl(moduleimpl_id: int):
|
||||
@permission_required(Permission.ScoView)
|
||||
@as_json
|
||||
def moduleimpl_inscriptions(moduleimpl_id: int):
|
||||
"""Liste des inscriptions à ce moduleimpl
|
||||
Exemple de résultat :
|
||||
[
|
||||
{
|
||||
"id": 1,
|
||||
"etudid": 666,
|
||||
"moduleimpl_id": 1234,
|
||||
},
|
||||
...
|
||||
]
|
||||
"""Liste des inscriptions à ce moduleimpl.
|
||||
|
||||
SAMPLES
|
||||
-------
|
||||
/moduleimpl/1/inscriptions
|
||||
"""
|
||||
modimpl = ModuleImpl.get_modimpl(moduleimpl_id)
|
||||
return [i.to_dict() for i in modimpl.inscriptions]
|
||||
@ -93,22 +69,11 @@ def moduleimpl_inscriptions(moduleimpl_id: int):
|
||||
@scodoc
|
||||
@permission_required(Permission.ScoView)
|
||||
def moduleimpl_notes(moduleimpl_id: int):
|
||||
"""Liste des notes dans ce moduleimpl
|
||||
Exemple de résultat :
|
||||
[
|
||||
{
|
||||
"etudid": 17776, // code de l'étudiant
|
||||
"nom": "DUPONT",
|
||||
"prenom": "Luz",
|
||||
"38411": 16.0, // Note dans l'évaluation d'id 38411
|
||||
"38410": 15.0,
|
||||
"moymod": 15.5, // Moyenne INDICATIVE module
|
||||
"moy_ue_2875": 15.5, // Moyenne vers l'UE 2875
|
||||
"moy_ue_2876": 15.5, // Moyenne vers l'UE 2876
|
||||
"moy_ue_2877": 15.5 // Moyenne vers l'UE 2877
|
||||
},
|
||||
...
|
||||
]
|
||||
"""Liste des notes dans ce moduleimpl.
|
||||
|
||||
SAMPLES
|
||||
-------
|
||||
/moduleimpl/1/notes
|
||||
"""
|
||||
modimpl = ModuleImpl.get_modimpl(moduleimpl_id)
|
||||
app.set_sco_dept(modimpl.formsemestre.departement.acronym)
|
||||
|
@ -6,6 +6,11 @@
|
||||
|
||||
"""
|
||||
ScoDoc 9 API : partitions
|
||||
|
||||
CATEGORY
|
||||
--------
|
||||
Groupes et Partitions
|
||||
|
||||
"""
|
||||
from operator import attrgetter
|
||||
|
||||
@ -40,22 +45,9 @@ from app.scodoc import sco_utils as scu
|
||||
def partition_info(partition_id: int):
|
||||
"""Info sur une partition.
|
||||
|
||||
Exemple de résultat :
|
||||
```
|
||||
{
|
||||
'bul_show_rank': False,
|
||||
'formsemestre_id': 39,
|
||||
'groups': [
|
||||
{'id': 268, 'name': 'A', 'partition_id': 100},
|
||||
{'id': 269, 'name': 'B', 'partition_id': 100}
|
||||
],
|
||||
'groups_editable': True,
|
||||
'id': 100,
|
||||
'numero': 100,
|
||||
'partition_name': 'TD',
|
||||
'show_in_lists': True
|
||||
}
|
||||
```
|
||||
SAMPLES
|
||||
-------
|
||||
/partition/1
|
||||
"""
|
||||
query = Partition.query.filter_by(id=partition_id)
|
||||
if g.scodoc_dept:
|
||||
@ -71,24 +63,11 @@ def partition_info(partition_id: int):
|
||||
@permission_required(Permission.ScoView)
|
||||
@as_json
|
||||
def formsemestre_partitions(formsemestre_id: int):
|
||||
"""Liste de toutes les partitions d'un formsemestre
|
||||
|
||||
formsemestre_id : l'id d'un formsemestre
|
||||
|
||||
{
|
||||
partition_id : {
|
||||
"bul_show_rank": False,
|
||||
"formsemestre_id": 1063,
|
||||
"groups" :
|
||||
group_id : {
|
||||
"id" : 12,
|
||||
"name" : "A",
|
||||
"partition_id" : partition_id,
|
||||
}
|
||||
},
|
||||
...
|
||||
}
|
||||
"""Liste de toutes les partitions d'un formsemestre.
|
||||
|
||||
SAMPLES
|
||||
-------
|
||||
/formsemestre/1/partitions
|
||||
"""
|
||||
query = FormSemestre.query.filter_by(id=formsemestre_id)
|
||||
if g.scodoc_dept:
|
||||
@ -112,21 +91,14 @@ def group_etudiants(group_id: int):
|
||||
"""
|
||||
Retourne la liste des étudiants dans un groupe
|
||||
(inscrits au groupe et inscrits au semestre).
|
||||
|
||||
PARAMS
|
||||
------
|
||||
group_id : l'id d'un groupe
|
||||
|
||||
Exemple de résultat :
|
||||
[
|
||||
{
|
||||
'civilite': 'M',
|
||||
'id': 123456,
|
||||
'ine': None,
|
||||
'nip': '987654321',
|
||||
'nom': 'MARTIN',
|
||||
'nom_usuel': null,
|
||||
'prenom': 'JEAN'}
|
||||
},
|
||||
...
|
||||
]
|
||||
SAMPLES
|
||||
-------
|
||||
/group/1/etudiants
|
||||
"""
|
||||
query = GroupDescr.query.filter_by(id=group_id)
|
||||
if g.scodoc_dept:
|
||||
@ -152,11 +124,11 @@ def group_etudiants(group_id: int):
|
||||
@permission_required(Permission.ScoView)
|
||||
@as_json
|
||||
def group_etudiants_query(group_id: int):
|
||||
"""Étudiants du groupe, filtrés par état (aucun, I, D, DEF)
|
||||
"""Étudiants du groupe, filtrés par état (aucun, `I`, `D`, `DEF`)
|
||||
|
||||
QUERY
|
||||
-----
|
||||
etat:<string:etat>
|
||||
etat : string
|
||||
|
||||
"""
|
||||
etat = request.args.get("etat")
|
||||
@ -186,7 +158,7 @@ def group_etudiants_query(group_id: int):
|
||||
@permission_required(Permission.ScoView)
|
||||
@as_json
|
||||
def group_set_etudiant(group_id: int, etudid: int):
|
||||
"""Affecte l'étudiant au groupe indiqué"""
|
||||
"""Affecte l'étudiant au groupe indiqué."""
|
||||
etud = Identite.query.get_or_404(etudid)
|
||||
query = GroupDescr.query.filter_by(id=group_id)
|
||||
if g.scodoc_dept:
|
||||
@ -248,7 +220,8 @@ def group_remove_etud(group_id: int, etudid: int):
|
||||
@permission_required(Permission.ScoView)
|
||||
@as_json
|
||||
def partition_remove_etud(partition_id: int, etudid: int):
|
||||
"""Enlève l'étudiant de tous les groupes de cette partition
|
||||
"""Enlève l'étudiant de tous les groupes de cette partition.
|
||||
|
||||
(NB: en principe, un étudiant ne doit être que dans 0 ou 1 groupe d'une partition)
|
||||
"""
|
||||
etud = Identite.query.get_or_404(etudid)
|
||||
@ -293,12 +266,19 @@ def partition_remove_etud(partition_id: int, etudid: int):
|
||||
@permission_required(Permission.ScoView)
|
||||
@as_json
|
||||
def group_create(partition_id: int): # partition-group-create
|
||||
"""Création d'un groupe dans une partition
|
||||
"""Création d'un groupe dans une partition.
|
||||
|
||||
The request content type should be "application/json":
|
||||
DATA
|
||||
----
|
||||
```json
|
||||
{
|
||||
"group_name" : nom_du_groupe,
|
||||
}
|
||||
```
|
||||
|
||||
SAMPLES
|
||||
-------
|
||||
/partition/1/group/create;{""group_name"" : ""Nouveau Groupe""}
|
||||
"""
|
||||
query = Partition.query.filter_by(id=partition_id)
|
||||
if g.scodoc_dept:
|
||||
@ -345,7 +325,7 @@ def group_create(partition_id: int): # partition-group-create
|
||||
@permission_required(Permission.ScoView)
|
||||
@as_json
|
||||
def group_delete(group_id: int):
|
||||
"""Suppression d'un groupe"""
|
||||
"""Suppression d'un groupe."""
|
||||
query = GroupDescr.query.filter_by(id=group_id)
|
||||
if g.scodoc_dept:
|
||||
query = (
|
||||
@ -374,7 +354,19 @@ def group_delete(group_id: int):
|
||||
@permission_required(Permission.ScoView)
|
||||
@as_json
|
||||
def group_edit(group_id: int):
|
||||
"""Edit a group"""
|
||||
"""Édition d'un groupe.
|
||||
|
||||
DATA
|
||||
----
|
||||
```json
|
||||
{
|
||||
"group_name" : "A1"
|
||||
}
|
||||
|
||||
SAMPLES
|
||||
-------
|
||||
/group/1/edit;{""group_name"":""A1""}
|
||||
"""
|
||||
query = GroupDescr.query.filter_by(id=group_id)
|
||||
if g.scodoc_dept:
|
||||
query = (
|
||||
@ -415,9 +407,14 @@ def group_edit(group_id: int):
|
||||
@permission_required(Permission.ScoView)
|
||||
@as_json
|
||||
def group_set_edt_id(group_id: int, edt_id: str):
|
||||
"""Set edt_id for this group.
|
||||
Contrairement à /edit, peut-être changé pour toute partition
|
||||
ou formsemestre non verrouillé.
|
||||
"""Set edt_id du groupe.
|
||||
|
||||
Contrairement à `/edit`, peut-être changé pour toute partition
|
||||
d'un formsemestre non verrouillé.
|
||||
|
||||
SAMPLES
|
||||
-------
|
||||
/group/1/set_edt_id/EDT_GR1
|
||||
"""
|
||||
query = GroupDescr.query.filter_by(id=group_id)
|
||||
if g.scodoc_dept:
|
||||
@ -443,16 +440,19 @@ def group_set_edt_id(group_id: int, edt_id: str):
|
||||
@permission_required(Permission.ScoView)
|
||||
@as_json
|
||||
def partition_create(formsemestre_id: int):
|
||||
"""Création d'une partition dans un semestre
|
||||
"""Création d'une partition dans un semestre.
|
||||
|
||||
The request content type should be "application/json":
|
||||
DATA
|
||||
----
|
||||
```json
|
||||
{
|
||||
"partition_name": str,
|
||||
"numero":int,
|
||||
"bul_show_rank":bool,
|
||||
"show_in_lists":bool,
|
||||
"groups_editable":bool
|
||||
"numero": int,
|
||||
"bul_show_rank": bool,
|
||||
"show_in_lists": bool,
|
||||
"groups_editable": bool
|
||||
}
|
||||
```
|
||||
"""
|
||||
query = FormSemestre.query.filter_by(id=formsemestre_id)
|
||||
if g.scodoc_dept:
|
||||
@ -508,8 +508,13 @@ def partition_create(formsemestre_id: int):
|
||||
@permission_required(Permission.ScoView)
|
||||
@as_json
|
||||
def formsemestre_set_partitions_order(formsemestre_id: int):
|
||||
"""Modifie l'ordre des partitions du formsemestre
|
||||
JSON args: [partition_id1, partition_id2, ...]
|
||||
"""Modifie l'ordre des partitions du formsemestre.
|
||||
|
||||
DATA
|
||||
----
|
||||
```json
|
||||
[ partition_id1, partition_id2, ... ]
|
||||
```
|
||||
"""
|
||||
query = FormSemestre.query.filter_by(id=formsemestre_id)
|
||||
if g.scodoc_dept:
|
||||
@ -520,7 +525,7 @@ def formsemestre_set_partitions_order(formsemestre_id: int):
|
||||
if not formsemestre.can_change_groups():
|
||||
return json_error(401, "opération non autorisée")
|
||||
partition_ids = request.get_json(force=True) # may raise 400 Bad Request
|
||||
if not isinstance(partition_ids, int) and not all(
|
||||
if not isinstance(partition_ids, list) and not all(
|
||||
isinstance(x, int) for x in partition_ids
|
||||
):
|
||||
return json_error(
|
||||
@ -549,8 +554,13 @@ def formsemestre_set_partitions_order(formsemestre_id: int):
|
||||
@permission_required(Permission.ScoView)
|
||||
@as_json
|
||||
def partition_order_groups(partition_id: int):
|
||||
"""Modifie l'ordre des groupes de la partition
|
||||
JSON args: [group_id1, group_id2, ...]
|
||||
"""Modifie l'ordre des groupes de la partition.
|
||||
|
||||
DATA
|
||||
----
|
||||
```json
|
||||
[ group_id1, group_id2, ... ]
|
||||
```
|
||||
"""
|
||||
query = Partition.query.filter_by(id=partition_id)
|
||||
if g.scodoc_dept:
|
||||
@ -561,7 +571,7 @@ def partition_order_groups(partition_id: int):
|
||||
if not partition.formsemestre.can_change_groups():
|
||||
return json_error(401, "opération non autorisée")
|
||||
group_ids = request.get_json(force=True) # may raise 400 Bad Request
|
||||
if not isinstance(group_ids, int) and not all(
|
||||
if not isinstance(group_ids, list) and not all(
|
||||
isinstance(x, int) for x in group_ids
|
||||
):
|
||||
return json_error(
|
||||
@ -586,10 +596,13 @@ def partition_order_groups(partition_id: int):
|
||||
@permission_required(Permission.ScoView)
|
||||
@as_json
|
||||
def partition_edit(partition_id: int):
|
||||
"""Modification d'une partition dans un semestre
|
||||
"""Modification d'une partition dans un semestre.
|
||||
|
||||
The request content type should be "application/json"
|
||||
All fields are optional:
|
||||
Tous les champs sont optionnels.
|
||||
|
||||
DATA
|
||||
----
|
||||
```json
|
||||
{
|
||||
"partition_name": str,
|
||||
"numero":int,
|
||||
@ -597,6 +610,11 @@ def partition_edit(partition_id: int):
|
||||
"show_in_lists":bool,
|
||||
"groups_editable":bool
|
||||
}
|
||||
```
|
||||
|
||||
SAMPLES
|
||||
-------
|
||||
/partition/1/edit;{""bul_show_rank"":1}
|
||||
"""
|
||||
query = Partition.query.filter_by(id=partition_id)
|
||||
if g.scodoc_dept:
|
||||
@ -631,9 +649,8 @@ def partition_edit(partition_id: int):
|
||||
|
||||
for boolean_field in ("bul_show_rank", "show_in_lists", "groups_editable"):
|
||||
value = data.get(boolean_field)
|
||||
value = scu.to_bool(value) if value is not None else None
|
||||
if value is not None and value != getattr(partition, boolean_field):
|
||||
if not isinstance(value, bool):
|
||||
return json_error(API_CLIENT_ERROR, f"invalid type for {boolean_field}")
|
||||
if boolean_field == "groups_editable" and partition.is_parcours():
|
||||
return json_error(
|
||||
API_CLIENT_ERROR, f"can't change {scu.PARTITION_PARCOURS}"
|
||||
@ -660,9 +677,9 @@ def partition_edit(partition_id: int):
|
||||
def partition_delete(partition_id: int):
|
||||
"""Suppression d'une partition (et de tous ses groupes).
|
||||
|
||||
Note 1: La partition par défaut (tous les étudiants du sem.) ne peut
|
||||
* Note 1: La partition par défaut (tous les étudiants du sem.) ne peut
|
||||
pas être supprimée.
|
||||
Note 2: Si la partition de parcours est supprimée, les étudiants
|
||||
* Note 2: Si la partition de parcours est supprimée, les étudiants
|
||||
sont désinscrits des parcours.
|
||||
"""
|
||||
query = Partition.query.filter_by(id=partition_id)
|
||||
|
@ -3,12 +3,18 @@ from app import db, log
|
||||
from app.api import api_bp as bp
|
||||
from app.auth.logic import basic_auth, token_auth
|
||||
|
||||
"""
|
||||
CATEGORY
|
||||
--------
|
||||
Authentification API
|
||||
"""
|
||||
|
||||
|
||||
@bp.route("/tokens", methods=["POST"])
|
||||
@basic_auth.login_required
|
||||
@as_json
|
||||
def token_get():
|
||||
"renvoie un jeton jwt pour l'utilisateur courant"
|
||||
"Renvoie un jeton jwt pour l'utilisateur courant."
|
||||
token = basic_auth.current_user().get_token()
|
||||
log(f"API: giving token to {basic_auth.current_user()}")
|
||||
db.session.commit()
|
||||
@ -18,7 +24,7 @@ def token_get():
|
||||
@bp.route("/tokens", methods=["DELETE"])
|
||||
@token_auth.login_required
|
||||
def token_revoke():
|
||||
"révoque le jeton de l'utilisateur courant"
|
||||
"Révoque le jeton de l'utilisateur courant."
|
||||
user = token_auth.current_user()
|
||||
user.revoke_token()
|
||||
db.session.commit()
|
||||
|
100
app/api/users.py
100
app/api/users.py
@ -6,6 +6,10 @@
|
||||
|
||||
"""
|
||||
ScoDoc 9 API : accès aux utilisateurs
|
||||
|
||||
CATEGORY
|
||||
--------
|
||||
Utilisateurs
|
||||
"""
|
||||
|
||||
from flask import g, request
|
||||
@ -32,7 +36,11 @@ from app.scodoc.sco_utils import json_error
|
||||
@as_json
|
||||
def user_info(uid: int):
|
||||
"""
|
||||
Info sur un compte utilisateur scodoc
|
||||
Info sur un compte utilisateur ScoDoc.
|
||||
|
||||
SAMPLES
|
||||
-------
|
||||
/user/2
|
||||
"""
|
||||
user: User = db.session.get(User, uid)
|
||||
if user is None:
|
||||
@ -53,7 +61,11 @@ def user_info(uid: int):
|
||||
@as_json
|
||||
def users_info_query():
|
||||
"""Utilisateurs, filtrés par dept, active ou début nom
|
||||
|
||||
Exemple:
|
||||
```
|
||||
/users/query?departement=dept_acronym&active=1&starts_with=<string:nom>
|
||||
```
|
||||
|
||||
Seuls les utilisateurs "accessibles" (selon les permissions) sont retournés.
|
||||
Si accès via API web, le département de l'URL est ignoré, seules
|
||||
@ -61,9 +73,9 @@ def users_info_query():
|
||||
|
||||
QUERY
|
||||
-----
|
||||
active:<bool:active>
|
||||
departement:<string:departement>
|
||||
starts_with:<string:starts_with>
|
||||
active: bool
|
||||
departement: string
|
||||
starts_with: string
|
||||
|
||||
"""
|
||||
query = User.query
|
||||
@ -113,7 +125,10 @@ def _is_allowed_user_edit(args: dict) -> tuple[bool, str]:
|
||||
@as_json
|
||||
def user_create():
|
||||
"""Création d'un utilisateur
|
||||
The request content type should be "application/json":
|
||||
|
||||
DATA
|
||||
----
|
||||
```json
|
||||
{
|
||||
"active":bool (default True),
|
||||
"dept": str or null,
|
||||
@ -122,6 +137,7 @@ def user_create():
|
||||
"user_name": str,
|
||||
...
|
||||
}
|
||||
```
|
||||
"""
|
||||
args = request.get_json(force=True) # may raise 400 Bad Request
|
||||
user_name = args.get("user_name")
|
||||
@ -158,8 +174,10 @@ def user_create():
|
||||
@permission_required(Permission.UsersAdmin)
|
||||
@as_json
|
||||
def user_edit(uid: int):
|
||||
"""Modification d'un utilisateur
|
||||
"""Modification d'un utilisateur.
|
||||
|
||||
Champs modifiables:
|
||||
```json
|
||||
{
|
||||
"dept": str or null,
|
||||
"nom": str,
|
||||
@ -167,6 +185,7 @@ def user_edit(uid: int):
|
||||
"active":bool
|
||||
...
|
||||
}
|
||||
```
|
||||
"""
|
||||
args = request.get_json(force=True) # may raise 400 Bad Request
|
||||
user: User = User.query.get_or_404(uid)
|
||||
@ -205,12 +224,21 @@ def user_edit(uid: int):
|
||||
@permission_required(Permission.UsersAdmin)
|
||||
@as_json
|
||||
def user_password(uid: int):
|
||||
"""Modification du mot de passe d'un utilisateur
|
||||
Champs modifiables:
|
||||
"""Modification du mot de passe d'un utilisateur.
|
||||
|
||||
Si le mot de passe ne convient pas, erreur 400.
|
||||
|
||||
DATA
|
||||
----
|
||||
```json
|
||||
{
|
||||
"password": str
|
||||
}
|
||||
Si le mot de passe ne convient pas, erreur 400.
|
||||
```
|
||||
|
||||
SAMPLES
|
||||
-------
|
||||
/user/3/password;{""password"" : ""rePlaCemeNT456averylongandcomplicated""}
|
||||
"""
|
||||
data = request.get_json(force=True) # may raise 400 Bad Request
|
||||
user: User = User.query.get_or_404(uid)
|
||||
@ -243,7 +271,7 @@ def user_password(uid: int):
|
||||
@permission_required(Permission.ScoSuperAdmin)
|
||||
@as_json
|
||||
def user_role_add(uid: int, role_name: str, dept: str = None):
|
||||
"""Add a role in the given dept to the user"""
|
||||
"""Ajoute un rôle à l'utilisateur dans le département donné."""
|
||||
user: User = User.query.get_or_404(uid)
|
||||
role: Role = Role.query.filter_by(name=role_name).first_or_404()
|
||||
if dept is not None: # check
|
||||
@ -272,7 +300,7 @@ def user_role_add(uid: int, role_name: str, dept: str = None):
|
||||
@permission_required(Permission.ScoSuperAdmin)
|
||||
@as_json
|
||||
def user_role_remove(uid: int, role_name: str, dept: str = None):
|
||||
"""Remove the role (in the given dept) from the user"""
|
||||
"""Retire le rôle (dans le département donné) à cet utilisateur."""
|
||||
user: User = User.query.get_or_404(uid)
|
||||
role: Role = Role.query.filter_by(name=role_name).first_or_404()
|
||||
if dept is not None: # check
|
||||
@ -299,7 +327,12 @@ def user_role_remove(uid: int, role_name: str, dept: str = None):
|
||||
@permission_required(Permission.UsersView)
|
||||
@as_json
|
||||
def permissions_list():
|
||||
"""Liste des noms de permissions définies"""
|
||||
"""Liste des noms de permissions définies.
|
||||
|
||||
SAMPLES
|
||||
-------
|
||||
/permissions
|
||||
"""
|
||||
return list(Permission.permission_by_name.keys())
|
||||
|
||||
|
||||
@ -310,7 +343,12 @@ def permissions_list():
|
||||
@permission_required(Permission.UsersView)
|
||||
@as_json
|
||||
def role_get(role_name: str):
|
||||
"""Un rôle"""
|
||||
"""Un rôle.
|
||||
|
||||
SAMPLES
|
||||
-------
|
||||
/role/Ens
|
||||
"""
|
||||
return Role.query.filter_by(name=role_name).first_or_404().to_dict()
|
||||
|
||||
|
||||
@ -321,7 +359,12 @@ def role_get(role_name: str):
|
||||
@permission_required(Permission.UsersView)
|
||||
@as_json
|
||||
def roles_list():
|
||||
"""Tous les rôles définis"""
|
||||
"""Tous les rôles définis.
|
||||
|
||||
SAMPLES
|
||||
-------
|
||||
/roles
|
||||
"""
|
||||
return [role.to_dict() for role in Role.query]
|
||||
|
||||
|
||||
@ -338,7 +381,7 @@ def roles_list():
|
||||
@permission_required(Permission.ScoSuperAdmin)
|
||||
@as_json
|
||||
def role_permission_add(role_name: str, perm_name: str):
|
||||
"""Add permission to role"""
|
||||
"""Ajoute une permission à un rôle."""
|
||||
role: Role = Role.query.filter_by(name=role_name).first_or_404()
|
||||
permission = Permission.get_by_name(perm_name)
|
||||
if permission is None:
|
||||
@ -363,7 +406,7 @@ def role_permission_add(role_name: str, perm_name: str):
|
||||
@permission_required(Permission.ScoSuperAdmin)
|
||||
@as_json
|
||||
def role_permission_remove(role_name: str, perm_name: str):
|
||||
"""Remove permission from role"""
|
||||
"""Retire une permission d'un rôle."""
|
||||
role: Role = Role.query.filter_by(name=role_name).first_or_404()
|
||||
permission = Permission.get_by_name(perm_name)
|
||||
if permission is None:
|
||||
@ -382,10 +425,19 @@ def role_permission_remove(role_name: str, perm_name: str):
|
||||
@permission_required(Permission.ScoSuperAdmin)
|
||||
@as_json
|
||||
def role_create(role_name: str):
|
||||
"""Create a new role with permissions.
|
||||
"""Création d'un nouveau rôle avec les permissions données.
|
||||
|
||||
DATA
|
||||
----
|
||||
```json
|
||||
{
|
||||
"permissions" : [ 'ScoView', ... ]
|
||||
}
|
||||
```
|
||||
|
||||
SAMPLES
|
||||
-------
|
||||
/role/create/customRole;{""permissions"": [""ScoView"", ""UsersView""]}
|
||||
"""
|
||||
role: Role = Role.query.filter_by(name=role_name).first()
|
||||
if role:
|
||||
@ -410,11 +462,16 @@ def role_create(role_name: str):
|
||||
@permission_required(Permission.ScoSuperAdmin)
|
||||
@as_json
|
||||
def role_edit(role_name: str):
|
||||
"""Edit a role. On peut spécifier un nom et/ou des permissions.
|
||||
"""Édition d'un rôle. On peut spécifier un nom et/ou des permissions.
|
||||
|
||||
DATA
|
||||
----
|
||||
```json
|
||||
{
|
||||
"name" : name
|
||||
"permissions" : [ 'ScoView', ... ]
|
||||
}
|
||||
```
|
||||
"""
|
||||
role: Role = Role.query.filter_by(name=role_name).first_or_404()
|
||||
data = request.get_json(force=True) # may raise 400 Bad Request
|
||||
@ -442,7 +499,12 @@ def role_edit(role_name: str):
|
||||
@permission_required(Permission.ScoSuperAdmin)
|
||||
@as_json
|
||||
def role_delete(role_name: str):
|
||||
"""Delete a role"""
|
||||
"""Suppression d'un rôle.
|
||||
|
||||
SAMPLES
|
||||
-------
|
||||
/role/customRole/delete
|
||||
"""
|
||||
role: Role = Role.query.filter_by(name=role_name).first_or_404()
|
||||
db.session.delete(role)
|
||||
db.session.commit()
|
||||
|
@ -348,7 +348,7 @@ class Assiduite(ScoDocModel):
|
||||
"""
|
||||
Retourne le module associé à l'assiduité
|
||||
Si traduire est vrai, retourne le titre du module précédé du code
|
||||
Sinon rentourne l'objet Module ou None
|
||||
Sinon retourne l'objet Module ou None
|
||||
"""
|
||||
|
||||
if self.moduleimpl_id is not None:
|
||||
@ -358,7 +358,7 @@ class Assiduite(ScoDocModel):
|
||||
return f"{mod.code} {mod.titre}"
|
||||
return mod
|
||||
|
||||
elif self.external_data is not None and "module" in self.external_data:
|
||||
if self.external_data is not None and "module" in self.external_data:
|
||||
return (
|
||||
"Autre module (pas dans la liste)"
|
||||
if self.external_data["module"] == "Autre"
|
||||
|
@ -52,6 +52,17 @@ class Departement(db.Model):
|
||||
def __repr__(self):
|
||||
return f"<{self.__class__.__name__}(id={self.id}, acronym='{self.acronym}')>"
|
||||
|
||||
@classmethod
|
||||
def get_departement(cls, dept_ident: str | int) -> "Departement":
|
||||
"Le département, 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 cls.query.filter_by(acronym=dept_ident).first_or_404()
|
||||
return cls.query.get_or_404(dept_id)
|
||||
|
||||
def to_dict(self, with_dept_name=True, with_dept_preferences=False):
|
||||
data = {
|
||||
"id": self.id,
|
||||
|
@ -242,6 +242,16 @@ class GroupDescr(ScoDocModel):
|
||||
f"""<{self.__class__.__name__} {self.id} "{self.group_name or '(tous)'}">"""
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def filter_model_attributes(cls, data: dict, excluded: set[str] = None) -> dict:
|
||||
"""Returns a copy of dict with only the keys belonging to the Model and not in excluded.
|
||||
Exclude `partition_id` : a group cannot be moved from a partition to another.
|
||||
"""
|
||||
return super().filter_model_attributes(
|
||||
data,
|
||||
excluded=(excluded or set()) | {"partition_id"},
|
||||
)
|
||||
|
||||
def get_nom_with_part(self, default="-") -> str:
|
||||
"""Nom avec partition: 'TD A'
|
||||
Si groupe par défaut (tous), utilise default ou "-"
|
||||
|
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)
|
32
app/templates/doc/apidoc.j2
Normal file
32
app/templates/doc/apidoc.j2
Normal file
@ -0,0 +1,32 @@
|
||||
{# Template pour la doc mardown d'un point d'entrée de l'API #}
|
||||
#### **`{{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 %}
|
10
scodoc.py
10
scodoc.py
@ -744,10 +744,10 @@ def generate_ens_calendars(): # generate-ens-calendars
|
||||
@click.option(
|
||||
"-e",
|
||||
"--endpoint",
|
||||
default="api",
|
||||
help="Endpoint à partir duquel générer la carte des routes",
|
||||
default="api.",
|
||||
help="Endpoint à partir duquel générer la documentation des routes",
|
||||
)
|
||||
@with_appcontext
|
||||
def gen_api_map(endpoint):
|
||||
"""Génère la carte des routes de l'API."""
|
||||
tools.gen_api_map(app, endpoint_start=endpoint)
|
||||
def gen_api_doc(endpoint): # gen-api-map
|
||||
"""Génère la documentation des routes de l'API."""
|
||||
tools.gen_api_doc(app, endpoint_start=endpoint)
|
||||
|
40
tests/api/api_shell.py
Normal file
40
tests/api/api_shell.py
Normal file
@ -0,0 +1,40 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
"""Script pour tester l'API en mode interactif
|
||||
|
||||
Utilisation:
|
||||
```py
|
||||
python -i tests/api/api_shell.py
|
||||
```
|
||||
|
||||
"""
|
||||
|
||||
import pdb
|
||||
from pprint import pprint as pp
|
||||
|
||||
from setup_test_api import (
|
||||
API_PASSWORD,
|
||||
API_URL,
|
||||
API_USER,
|
||||
APIError,
|
||||
CHECK_CERTIFICATE,
|
||||
get_auth_headers,
|
||||
GET,
|
||||
POST,
|
||||
SCODOC_URL,
|
||||
set_headers,
|
||||
)
|
||||
|
||||
set_headers(get_auth_headers("admin_api", "admin_api"))
|
||||
|
||||
print(
|
||||
"""
|
||||
Connecté au serveur ScoDoc. Vous pouvez utiliser:
|
||||
GET( route )
|
||||
POST( route, data )
|
||||
Exemple avec pretty print:
|
||||
pp(GET("/departements")[0])
|
||||
"""
|
||||
)
|
||||
|
||||
pp(GET("/departements")[0])
|
@ -30,7 +30,7 @@ from setup_test_api import (
|
||||
CHECK_CERTIFICATE,
|
||||
get_auth_headers,
|
||||
GET,
|
||||
POST_JSON,
|
||||
POST,
|
||||
SCODOC_URL,
|
||||
)
|
||||
|
||||
|
@ -23,7 +23,7 @@ from setup_test_api import (
|
||||
CHECK_CERTIFICATE,
|
||||
get_auth_headers,
|
||||
GET,
|
||||
POST_JSON,
|
||||
POST,
|
||||
SCODOC_URL,
|
||||
)
|
||||
|
||||
|
@ -39,7 +39,7 @@ from setup_test_api import (
|
||||
CHECK_CERTIFICATE,
|
||||
get_auth_headers,
|
||||
GET,
|
||||
POST_JSON,
|
||||
POST,
|
||||
SCODOC_URL,
|
||||
)
|
||||
|
||||
@ -127,14 +127,12 @@ group_id = 5315
|
||||
POST(f"/group/{group_id}/set_etudiant/{etudid}", headers=HEADERS)
|
||||
|
||||
|
||||
POST_JSON(
|
||||
f"/partition/{pid}/group/create", data={"group_name": "Omega10"}, headers=HEADERS
|
||||
)
|
||||
POST(f"/partition/{pid}/group/create", data={"group_name": "Omega10"}, headers=HEADERS)
|
||||
partitions = GET(f"/formsemestre/{formsemestre_id}/partitions", headers=HEADERS)
|
||||
pp(partitions)
|
||||
|
||||
POST_JSON(f"/group/5559/delete", headers=HEADERS)
|
||||
POST_JSON(f"/group/5327/edit", data={"group_name": "TDXXX"}, headers=HEADERS)
|
||||
POST(f"/group/5559/delete", headers=HEADERS)
|
||||
POST(f"/group/5327/edit", data={"group_name": "TDXXX"}, headers=HEADERS)
|
||||
|
||||
# --------- Toutes les bulletins, un à un, et les décisions de jury d'un semestre
|
||||
formsemestre_id = 911
|
||||
@ -178,19 +176,19 @@ etud = GET(f"/formsemestre/{formsemestre_id}/etudiants", headers=HEADERS)[10]
|
||||
etudid = etud["id"]
|
||||
|
||||
# 1- Crée une partition, puis la change de nom
|
||||
js = POST_JSON(
|
||||
js = POST(
|
||||
f"/formsemestre/{formsemestre_id}/partition/create",
|
||||
data={"partition_name": "PART"},
|
||||
)
|
||||
partition_id = js["id"]
|
||||
POST_JSON(
|
||||
POST(
|
||||
f"/partition/{partition_id}/edit",
|
||||
data={"partition_name": "PART1", "show_in_lists": True},
|
||||
headers=HEADERS,
|
||||
)
|
||||
|
||||
# 2- Crée un groupe
|
||||
js = POST_JSON(
|
||||
js = POST(
|
||||
f"/partition/{partition_id}/group/create",
|
||||
data={"group_name": "G1"},
|
||||
headers=HEADERS,
|
||||
@ -198,28 +196,28 @@ js = POST_JSON(
|
||||
group_1 = js["id"]
|
||||
|
||||
# 3- Crée deux autres groupes
|
||||
js = POST_JSON(
|
||||
js = POST(
|
||||
f"/partition/{partition_id}/group/create",
|
||||
data={"group_name": "G2"},
|
||||
headers=HEADERS,
|
||||
)
|
||||
js = POST_JSON(
|
||||
js = POST(
|
||||
f"/partition/{partition_id}/group/create",
|
||||
data={"group_name": "G3"},
|
||||
headers=HEADERS,
|
||||
)
|
||||
|
||||
# 4- Affecte étudiant au groupe G1
|
||||
POST_JSON(f"/group/{group_1}/set_etudiant/{etudid}", headers=HEADERS)
|
||||
POST(f"/group/{group_1}/set_etudiant/{etudid}", headers=HEADERS)
|
||||
|
||||
# 5- retire du groupe
|
||||
POST_JSON(f"/group/{group_1}/remove_etudiant/{etudid}", headers=HEADERS)
|
||||
POST(f"/group/{group_1}/remove_etudiant/{etudid}", headers=HEADERS)
|
||||
|
||||
# 6- affecte au groupe G2
|
||||
partition = GET(f"/partition/{partition_id}")
|
||||
assert len(partition["groups"]) == 3
|
||||
group_2 = [g for g in partition["groups"].values() if g["group_name"] == "G2"][0]["id"]
|
||||
POST_JSON(f"/group/{group_2}/set_etudiant/{etudid}", headers=HEADERS)
|
||||
POST(f"/group/{group_2}/set_etudiant/{etudid}", headers=HEADERS)
|
||||
|
||||
# 7- Membres du groupe
|
||||
etuds_g2 = GET(f"/group/{group_2}/etudiants", headers=HEADERS)
|
||||
@ -229,7 +227,7 @@ assert etuds_g2[0]["id"] == etudid
|
||||
# 8- Ordres des groupes
|
||||
group_3 = [g for g in partition["groups"].values() if g["group_name"] == "G3"][0]["id"]
|
||||
|
||||
POST_JSON(
|
||||
POST(
|
||||
f"/partition/{partition_id}/groups/order",
|
||||
data=[group_2, group_1, group_3],
|
||||
headers=HEADERS,
|
||||
@ -242,7 +240,7 @@ new_groups = [
|
||||
assert new_groups == [group_2, group_1, group_3]
|
||||
|
||||
# 9- Suppression
|
||||
POST_JSON(f"/partition/{partition_id}/delete")
|
||||
POST(f"/partition/{partition_id}/delete")
|
||||
# ------
|
||||
|
||||
# Tests accès API:
|
||||
@ -260,13 +258,13 @@ POST_JSON(f"/partition/{partition_id}/delete")
|
||||
"""
|
||||
|
||||
#
|
||||
POST_JSON(
|
||||
POST(
|
||||
"/partition/2264/groups/order",
|
||||
data=[5563, 5562, 5561, 5560, 5558, 5557, 5316, 5315],
|
||||
headers=HEADERS,
|
||||
)
|
||||
|
||||
POST_JSON(
|
||||
POST(
|
||||
"/formsemestre/1063/partitions/order",
|
||||
data=[2264, 2263, 2265, 2266, 2267, 2372, 2378],
|
||||
headers=HEADERS,
|
||||
|
@ -41,7 +41,7 @@ from setup_test_api import (
|
||||
CHECK_CERTIFICATE,
|
||||
get_auth_headers,
|
||||
GET,
|
||||
POST_JSON,
|
||||
POST,
|
||||
SCODOC_URL,
|
||||
)
|
||||
|
||||
|
@ -1,170 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- mode: python -*-
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# OBSOLETE - NE PLUS UTILISER CETTE API
|
||||
# VOIR https://scodoc.org/ScoDoc9API/
|
||||
|
||||
"""Exemple connexion sur ScoDoc 9 et utilisation de l'ancienne API ScoDoc 7
|
||||
à la mode "PHP": les gens passaient directement __ac_name et __ac_password
|
||||
dans chaque requête, en POST ou en GET.
|
||||
|
||||
Cela n'a jamais été documenté mais était implicitement supporté. C'est "deprecated"
|
||||
et ne sera plus supporté à partir de juillet 2022.
|
||||
|
||||
Ce script va tester:
|
||||
- Liste semestres
|
||||
- Liste modules
|
||||
- Creation d'une évaluation
|
||||
- Saisie d'une note
|
||||
|
||||
Utilisation: créer les variables d'environnement: (indiquer les valeurs
|
||||
pour le serveur ScoDoc que vous voulez interroger)
|
||||
|
||||
export SCODOC_URL="https://scodoc.xxx.net/"
|
||||
export SCODOC_USER="xxx"
|
||||
export SCODOC_PASSWD="xxx"
|
||||
export CHECK_CERTIFICATE=0 # ou 1 si serveur de production avec certif SSL valide
|
||||
|
||||
(on peut aussi placer ces valeurs dans un fichier .env du répertoire tests/api).
|
||||
"""
|
||||
|
||||
from dotenv import load_dotenv
|
||||
import json
|
||||
import os
|
||||
import pdb
|
||||
import requests
|
||||
import urllib3
|
||||
from pprint import pprint as pp
|
||||
|
||||
# --- Lecture configuration (variables d'env ou .env)
|
||||
BASEDIR = os.path.abspath(os.path.dirname(__file__))
|
||||
load_dotenv(os.path.join(BASEDIR, ".env"))
|
||||
CHECK_CERTIFICATE = bool(int(os.environ.get("CHECK_CERTIFICATE", False)))
|
||||
SCODOC_URL = os.environ["SCODOC_URL"]
|
||||
SCODOC_DEPT = os.environ["SCODOC_DEPT"]
|
||||
DEPT_URL = SCODOC_URL + "/ScoDoc/" + SCODOC_DEPT + "/Scolarite"
|
||||
SCODOC_USER = os.environ["SCODOC_USER"]
|
||||
SCODOC_PASSWORD = os.environ["SCODOC_PASSWD"]
|
||||
print(f"SCODOC_URL={SCODOC_URL}")
|
||||
|
||||
# ---
|
||||
if not CHECK_CERTIFICATE:
|
||||
urllib3.disable_warnings()
|
||||
|
||||
|
||||
class ScoError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
def GET(path: str, params=None, errmsg=None):
|
||||
"""Get and returns as JSON"""
|
||||
# ajoute auth
|
||||
params["__ac_name"] = SCODOC_USER
|
||||
params["__ac_password"] = SCODOC_PASSWORD
|
||||
r = requests.get(
|
||||
DEPT_URL + "/" + path, params=params, verify=CHECK_CERTIFICATE, timeout=10
|
||||
)
|
||||
if r.status_code != 200:
|
||||
raise ScoError(errmsg or "erreur !")
|
||||
return r.json() # decode la reponse JSON
|
||||
|
||||
|
||||
def POST(path: str, data: dict, errmsg=None):
|
||||
"""Post"""
|
||||
data["__ac_name"] = data.get("__ac_name", SCODOC_USER)
|
||||
data["__ac_password"] = data.get("__ac_password", SCODOC_PASSWORD)
|
||||
r = requests.post(
|
||||
DEPT_URL + "/" + path, data=data, verify=CHECK_CERTIFICATE, timeout=10
|
||||
)
|
||||
return r
|
||||
|
||||
|
||||
# ---
|
||||
# pas besoin d'ouvrir une session, on y va directement:
|
||||
|
||||
# --- Recupere la liste de tous les semestres:
|
||||
sems = GET("Notes/formsemestre_list", params={"fmt": "json"})
|
||||
|
||||
# sems est une liste de semestres (dictionnaires)
|
||||
for sem in sems:
|
||||
if sem["etat"]:
|
||||
break
|
||||
|
||||
if sem["etat"] == "0":
|
||||
raise ScoError("Aucun semestre non verrouillé !")
|
||||
|
||||
# Affiche le semestre trouvé:
|
||||
pp(sem)
|
||||
|
||||
# Liste des étudiants dans le 1er semestre non verrouillé:
|
||||
group_list = GET(
|
||||
"groups_view",
|
||||
params={
|
||||
"formsemestre_id": sem["formsemestre_id"],
|
||||
"with_codes": 1,
|
||||
"fmt": "json",
|
||||
},
|
||||
)
|
||||
if not group_list:
|
||||
# config inadaptée pour les tests...
|
||||
raise ScoError("aucun étudiant inscrit dans le semestre")
|
||||
|
||||
etud = group_list[0] # le premier étudiant inscrit ici
|
||||
# test un POST
|
||||
r = POST(
|
||||
"Absences/AddBilletAbsence",
|
||||
{
|
||||
"begin": "2021-10-25",
|
||||
"end": "2021-10-26",
|
||||
"description": "test API scodoc7",
|
||||
"etudid": etud["etudid"],
|
||||
},
|
||||
)
|
||||
assert r.status_code == 200
|
||||
assert r.text.startswith('<?xml version="1.0" encoding="utf-8"?>')
|
||||
assert "billet_id" in r.text
|
||||
# Essai avec un compte invalide
|
||||
r_invalid = POST(
|
||||
"Absences/AddBilletAbsence",
|
||||
{
|
||||
"__ac_name": "xxx",
|
||||
"begin": "2021-10-25",
|
||||
"end": "2021-10-26",
|
||||
"description": "test API scodoc7",
|
||||
"etudid": etud["etudid"],
|
||||
},
|
||||
)
|
||||
assert r_invalid.status_code == 403 # compte invalide => not authorized
|
||||
|
||||
# AddBilletAbsence en json
|
||||
r = POST(
|
||||
"Absences/AddBilletAbsence",
|
||||
{
|
||||
"begin": "2021-10-25",
|
||||
"end": "2021-10-26",
|
||||
"description": "test API scodoc7",
|
||||
"etudid": etud["etudid"],
|
||||
"xml_reply": 0,
|
||||
},
|
||||
)
|
||||
assert r.status_code == 200
|
||||
assert isinstance(json.loads(r.text)[0]["billet_id"], int)
|
||||
|
||||
# Les fonctions ci-dessous ne fonctionnent plus en ScoDoc 9
|
||||
# Voir https://scodoc.org/git/viennet/ScoDoc/issues/149
|
||||
|
||||
# # ---- Liste les modules et prend le premier
|
||||
# mods = GET("/Notes/moduleimpl_list", params={"formsemestre_id": sem["formsemestre_id"]})
|
||||
# print(f"{len(mods)} modules dans le semestre {sem['titre']}")
|
||||
|
||||
# mod = mods[0]
|
||||
|
||||
# # ---- Etudiants inscrits dans ce module
|
||||
# inscrits = GET(
|
||||
# "Notes/do_moduleimpl_inscription_list",
|
||||
# params={"moduleimpl_id": mod["moduleimpl_id"]},
|
||||
# )
|
||||
# print(f"{len(inscrits)} inscrits dans ce module")
|
||||
# # prend le premier inscrit, au hasard:
|
||||
# etudid = inscrits[0]["etudid"]
|
@ -10,15 +10,15 @@
|
||||
|
||||
Si entry_names est spécifié, la génération est restreinte aux exemples cités.
|
||||
Exemple:
|
||||
python make_samples departements departement-formsemestres
|
||||
python make_samples departements departement_formsemestres
|
||||
|
||||
Doit être exécutée immédiatement apres une initialisation de la base pour test API!
|
||||
Doit être exécutée immédiatement apres une initialisation de la base pour test API!
|
||||
(car dépendant des identifiants générés lors de la création des objets)
|
||||
|
||||
Modifer le /opt/scodoc/.env pour pointer sur la base test
|
||||
SCODOC_DATABASE_URI="postgresql:///SCODOC_TEST_API"
|
||||
|
||||
puis re-créer cette base
|
||||
puis re-créer cette base
|
||||
tools/create_database.sh --drop SCODOC_TEST_API
|
||||
flask db upgrade
|
||||
flask sco-db-init --erase
|
||||
@ -28,10 +28,10 @@ et lancer le serveur test:
|
||||
flask run --debug
|
||||
```
|
||||
|
||||
Cet utilitaire prend en argument le fichier de nom `samples.csv` contenant la description
|
||||
Cet utilitaire prend en argument le fichier de nom `samples.csv` contenant la description
|
||||
des exemples (séparés par une tabulation (\t), une ligne par exemple)
|
||||
* Le nom de l'exemple donne le nom du fichier généré (nom_exemple => nom_exemple.json.md).
|
||||
Plusieurs lignes peuvent partager le même nom. dans ce cas le fichier contiendra
|
||||
* Le nom de l'exemple donne le nom du fichier généré (nom_exemple => nom_exemple.json.md).
|
||||
Plusieurs lignes peuvent partager le même nom. dans ce cas le fichier contiendra
|
||||
chacun des exemples
|
||||
* l'url utilisée
|
||||
* la permission nécessaire (par défaut ScoView)
|
||||
@ -39,7 +39,7 @@ chacun des exemples
|
||||
* les arguments éventuel (en cas de POST): une chaîne de caractère selon json
|
||||
|
||||
Implémentation:
|
||||
Le code complète une structure de données (Samples) qui est un dictionnaire de set
|
||||
Le code complète une structure de données (Samples) qui est un dictionnaire de set
|
||||
(indicé par le nom des exemples).
|
||||
Chacun des éléments du set est un exemple (Sample)
|
||||
Quand la structure est complète, on génére tous les fichiers textes
|
||||
@ -48,7 +48,7 @@ Quand la structure est complète, on génére tous les fichiers textes
|
||||
- l'url utilisée
|
||||
- les arguments éventuels
|
||||
- le résultat
|
||||
Le tout mis en forme au format markdown et rangé dans le répertoire DATA_DIR (/tmp/samples)
|
||||
Le tout mis en forme au format markdown et rangé dans le répertoire DATA_DIR (/tmp/samples)
|
||||
qui est créé ou écrasé si déjà existant.
|
||||
"""
|
||||
import os
|
||||
@ -71,7 +71,7 @@ from setup_test_api import (
|
||||
CHECK_CERTIFICATE,
|
||||
get_auth_headers,
|
||||
GET,
|
||||
POST_JSON,
|
||||
POST,
|
||||
SCODOC_URL,
|
||||
)
|
||||
|
||||
@ -98,18 +98,23 @@ class Sample:
|
||||
elif permission == "UsersAdmin":
|
||||
HEADERS = get_auth_headers("admin_api", "admin_api")
|
||||
else:
|
||||
raise SampleException(f"Bad permission : {permission}")
|
||||
raise SampleException(f"Bad permission : {permission}, url={self.url}")
|
||||
if self.method == "GET":
|
||||
self.result = GET(self.url, HEADERS)
|
||||
elif self.method == "POST":
|
||||
if self.content == "":
|
||||
self.result = POST_JSON(self.url, headers=HEADERS)
|
||||
self.result = POST(self.url, headers=HEADERS)
|
||||
else:
|
||||
HEADERS["Content-Type"] = "application/json ; charset=utf-8"
|
||||
self.result = POST_JSON(self.url, json.loads(self.content), HEADERS)
|
||||
try:
|
||||
data = json.loads(self.content)
|
||||
except json.decoder.JSONDecodeError as exc:
|
||||
raise ValueError(
|
||||
f"JSON invalide: {self.content}\nurl={self.url}"
|
||||
) from exc
|
||||
self.result = POST(self.url, data, HEADERS)
|
||||
elif self.method[0] != "#":
|
||||
error = f'Bad method : "{self.method}"'
|
||||
raise SampleException(error)
|
||||
raise SampleException(f'Bad method : "{self.method}", url={self.url}')
|
||||
self.shorten()
|
||||
with open("sample_TEST.json.md", "tw", encoding="utf-8") as f:
|
||||
self.dump(f)
|
||||
@ -235,3 +240,5 @@ if not CHECK_CERTIFICATE:
|
||||
urllib3.disable_warnings()
|
||||
|
||||
make_samples(SAMPLES_FILENAME)
|
||||
|
||||
print(f"Fichiers samples générés dans {DATA_DIR}")
|
||||
|
@ -79,6 +79,16 @@ if pytest:
|
||||
return get_auth_headers(API_USER_ADMIN, API_PASSWORD_ADMIN)
|
||||
|
||||
|
||||
class _DefaultHeaders:
|
||||
headers = {}
|
||||
|
||||
|
||||
def set_headers(headers: dict):
|
||||
"""Set default headers"""
|
||||
print(f"set_headers: {headers}")
|
||||
_DefaultHeaders.headers = headers
|
||||
|
||||
|
||||
def GET(path: str, headers: dict = None, errmsg=None, dept=None, raw=False):
|
||||
"""Get and optionaly returns as JSON
|
||||
Special case for non json result (image or pdf):
|
||||
@ -91,7 +101,7 @@ def GET(path: str, headers: dict = None, errmsg=None, dept=None, raw=False):
|
||||
url = API_URL + path
|
||||
reply = requests.get(
|
||||
url,
|
||||
headers=headers or {},
|
||||
headers=_DefaultHeaders.headers if headers is None else headers,
|
||||
verify=CHECK_CERTIFICATE,
|
||||
timeout=SCO_TEST_API_TIMEOUT,
|
||||
)
|
||||
@ -119,7 +129,7 @@ def GET(path: str, headers: dict = None, errmsg=None, dept=None, raw=False):
|
||||
raise APIError("Unknown returned content {r.headers.get('Content-Type', None} !\n")
|
||||
|
||||
|
||||
def POST_JSON(
|
||||
def POST(
|
||||
path: str, data: dict = {}, headers: dict = None, errmsg=None, dept=None, raw=False
|
||||
):
|
||||
"""Post
|
||||
@ -132,7 +142,7 @@ def POST_JSON(
|
||||
r = requests.post(
|
||||
url,
|
||||
json=data,
|
||||
headers=headers or {},
|
||||
headers=_DefaultHeaders.headers if headers is None else headers,
|
||||
verify=CHECK_CERTIFICATE,
|
||||
timeout=SCO_TEST_API_TIMEOUT,
|
||||
)
|
||||
@ -200,7 +210,7 @@ def check_failure_post(path: str, headers: dict, data: dict, err: str = None):
|
||||
"""
|
||||
|
||||
try:
|
||||
data = POST_JSON(path=path, headers=headers, data=data, dept=DEPT_ACRONYM)
|
||||
data = POST(path=path, headers=headers, data=data, dept=DEPT_ACRONYM)
|
||||
# ^ Renvoie un 404
|
||||
except APIError as api_err:
|
||||
if err is not None:
|
||||
|
@ -10,7 +10,7 @@ from types import NoneType
|
||||
|
||||
from tests.api.setup_test_api import (
|
||||
GET,
|
||||
POST_JSON,
|
||||
POST,
|
||||
DEPT_ACRONYM,
|
||||
APIError,
|
||||
api_headers,
|
||||
@ -260,7 +260,7 @@ def test_route_create(api_admin_headers):
|
||||
# Bon fonctionnement
|
||||
data = create_data("present", "03")
|
||||
|
||||
res = POST_JSON(
|
||||
res = POST(
|
||||
f"/assiduite/{ETUDID}/create", [data], api_admin_headers, dept=DEPT_ACRONYM
|
||||
)
|
||||
check_fields(res, BATCH_FIELD)
|
||||
@ -275,7 +275,7 @@ def test_route_create(api_admin_headers):
|
||||
check_fields(data, fields=ASSIDUITES_FIELDS)
|
||||
|
||||
data2 = create_data("absent", "04", MODULE, "desc")
|
||||
res = POST_JSON(
|
||||
res = POST(
|
||||
f"/assiduite/{ETUDID}/create", [data2], api_admin_headers, dept=DEPT_ACRONYM
|
||||
)
|
||||
check_fields(res, BATCH_FIELD)
|
||||
@ -286,7 +286,7 @@ def test_route_create(api_admin_headers):
|
||||
# Mauvais fonctionnement
|
||||
check_failure_post(f"/assiduite/{FAUX}/create", api_admin_headers, [data])
|
||||
|
||||
res = POST_JSON(
|
||||
res = POST(
|
||||
f"/assiduite/{ETUDID}/create", [data], api_admin_headers, dept=DEPT_ACRONYM
|
||||
)
|
||||
check_fields(res, BATCH_FIELD)
|
||||
@ -296,7 +296,7 @@ def test_route_create(api_admin_headers):
|
||||
== "Duplication: la période rentre en conflit avec une plage enregistrée"
|
||||
)
|
||||
|
||||
res = POST_JSON(
|
||||
res = POST(
|
||||
f"/assiduite/{ETUDID}/create",
|
||||
[create_data("absent", "05", FAUX)],
|
||||
api_admin_headers,
|
||||
@ -316,7 +316,7 @@ def test_route_create(api_admin_headers):
|
||||
for d in range(randint(2, 4))
|
||||
]
|
||||
|
||||
res = POST_JSON(
|
||||
res = POST(
|
||||
f"/assiduite/{ETUDID}/create", data, api_admin_headers, dept=DEPT_ACRONYM
|
||||
)
|
||||
check_fields(res, BATCH_FIELD)
|
||||
@ -334,7 +334,7 @@ def test_route_create(api_admin_headers):
|
||||
create_data("absent", "01"),
|
||||
]
|
||||
|
||||
res = POST_JSON(
|
||||
res = POST(
|
||||
f"/assiduite/{ETUDID}/create", data2, api_admin_headers, dept=DEPT_ACRONYM
|
||||
)
|
||||
check_fields(res, BATCH_FIELD)
|
||||
@ -359,13 +359,13 @@ def test_route_edit(api_admin_headers):
|
||||
# Bon fonctionnement
|
||||
|
||||
data = {"etat": "retard", "moduleimpl_id": MODULE}
|
||||
res = POST_JSON(
|
||||
res = POST(
|
||||
f"/assiduite/{TO_REMOVE[0]}/edit", data, api_admin_headers, dept=DEPT_ACRONYM
|
||||
)
|
||||
assert res == {"OK": True}
|
||||
|
||||
data["moduleimpl_id"] = None
|
||||
res = POST_JSON(
|
||||
res = POST(
|
||||
f"/assiduite/{TO_REMOVE[1]}/edit", data, api_admin_headers, dept=DEPT_ACRONYM
|
||||
)
|
||||
assert res == {"OK": True}
|
||||
@ -389,13 +389,13 @@ def test_route_delete(api_admin_headers):
|
||||
# Bon fonctionnement
|
||||
data = TO_REMOVE[0]
|
||||
|
||||
res = POST_JSON("/assiduite/delete", [data], api_admin_headers, dept=DEPT_ACRONYM)
|
||||
res = POST("/assiduite/delete", [data], api_admin_headers, dept=DEPT_ACRONYM)
|
||||
check_fields(res, BATCH_FIELD)
|
||||
for dat in res["success"]:
|
||||
assert dat["message"] == "OK"
|
||||
|
||||
# Mauvais fonctionnement
|
||||
res = POST_JSON("/assiduite/delete", [data], api_admin_headers, dept=DEPT_ACRONYM)
|
||||
res = POST("/assiduite/delete", [data], api_admin_headers, dept=DEPT_ACRONYM)
|
||||
check_fields(res, BATCH_FIELD)
|
||||
assert len(res["errors"]) == 1
|
||||
|
||||
@ -405,7 +405,7 @@ def test_route_delete(api_admin_headers):
|
||||
|
||||
data = TO_REMOVE[1:]
|
||||
|
||||
res = POST_JSON("/assiduite/delete", data, api_admin_headers, dept=DEPT_ACRONYM)
|
||||
res = POST("/assiduite/delete", data, api_admin_headers, dept=DEPT_ACRONYM)
|
||||
check_fields(res, BATCH_FIELD)
|
||||
for dat in res["success"]:
|
||||
assert dat["message"] == "OK"
|
||||
@ -418,7 +418,7 @@ def test_route_delete(api_admin_headers):
|
||||
FAUX + 2,
|
||||
]
|
||||
|
||||
res = POST_JSON("/assiduite/delete", data2, api_admin_headers, dept=DEPT_ACRONYM)
|
||||
res = POST("/assiduite/delete", data2, api_admin_headers, dept=DEPT_ACRONYM)
|
||||
check_fields(res, BATCH_FIELD)
|
||||
assert len(res["errors"]) == 3
|
||||
|
||||
|
@ -8,7 +8,7 @@ Utilisation :
|
||||
import datetime
|
||||
import requests
|
||||
|
||||
from tests.api.setup_test_api import GET, POST_JSON, api_headers
|
||||
from tests.api.setup_test_api import GET, POST, api_headers
|
||||
|
||||
ETUDID = 1
|
||||
|
||||
@ -28,7 +28,7 @@ def test_billets(api_headers):
|
||||
abs_end="2022-08-01",
|
||||
description="test 1",
|
||||
)
|
||||
billet_r = POST_JSON("/billets_absence/create", billet_d, headers=api_headers)
|
||||
billet_r = POST("/billets_absence/create", billet_d, headers=api_headers)
|
||||
assert billet_r["etudid"] == billet_d["etudid"]
|
||||
assert datetime.datetime.fromisoformat(billet_r["abs_begin"]).replace(
|
||||
tzinfo=None
|
||||
@ -43,12 +43,10 @@ def test_billets(api_headers):
|
||||
abs_end="2022-08-03",
|
||||
description="test 2",
|
||||
)
|
||||
billet_r = POST_JSON("/billets_absence/create", billet_d2, headers=api_headers)
|
||||
billet_r = POST("/billets_absence/create", billet_d2, headers=api_headers)
|
||||
billets = GET("/billets_absence/etudiant/1", headers=api_headers)
|
||||
assert len(billets) == 2
|
||||
# Suppression
|
||||
for billet in billets:
|
||||
reply = POST_JSON(
|
||||
f"/billets_absence/{billet['id']}/delete", headers=api_headers
|
||||
)
|
||||
reply = POST(f"/billets_absence/{billet['id']}/delete", headers=api_headers)
|
||||
assert reply["OK"] == True
|
||||
|
@ -23,7 +23,7 @@ from tests.api.setup_test_api import (
|
||||
API_URL,
|
||||
CHECK_CERTIFICATE,
|
||||
GET,
|
||||
POST_JSON,
|
||||
POST,
|
||||
api_headers,
|
||||
api_admin_headers,
|
||||
)
|
||||
@ -42,7 +42,7 @@ def test_create_dept(api_admin_headers):
|
||||
/departement/<string:dept_acronym>/edit
|
||||
/departement/<string:dept_acronym>/delete
|
||||
"""
|
||||
dept = POST_JSON(
|
||||
dept = POST(
|
||||
"/departement/create",
|
||||
{"acronym": "XTEST", "visible": True},
|
||||
headers=api_admin_headers,
|
||||
@ -50,14 +50,14 @@ def test_create_dept(api_admin_headers):
|
||||
dept_r = GET(f"/departement/{dept['acronym']}", headers=api_admin_headers)
|
||||
assert dept["acronym"] == dept_r["acronym"]
|
||||
assert dept_r["visible"] is True
|
||||
dept_e = POST_JSON(
|
||||
dept_e = POST(
|
||||
f"/departement/{dept['acronym']}/edit",
|
||||
{"visible": False},
|
||||
headers=api_admin_headers,
|
||||
)
|
||||
dept_r = GET(f"/departement/{dept['acronym']}", headers=api_admin_headers)
|
||||
assert dept_r["visible"] is False
|
||||
r = POST_JSON(
|
||||
r = POST(
|
||||
f"/departement/{dept['acronym']}/delete",
|
||||
headers=api_admin_headers,
|
||||
)
|
||||
|
@ -30,7 +30,7 @@ from tests.api.setup_test_api import (
|
||||
CHECK_CERTIFICATE,
|
||||
DEPT_ACRONYM,
|
||||
GET,
|
||||
POST_JSON,
|
||||
POST,
|
||||
get_auth_headers,
|
||||
)
|
||||
from tests.api.setup_test_api import api_headers # pylint: disable=unused-import
|
||||
@ -262,7 +262,7 @@ def test_etudiants_by_name(api_headers):
|
||||
"dept": DEPT_ACRONYM,
|
||||
"civilite": "X",
|
||||
}
|
||||
_ = POST_JSON(
|
||||
_ = POST(
|
||||
"/etudiant/create",
|
||||
args,
|
||||
headers=admin_header,
|
||||
@ -292,7 +292,7 @@ def test_etudiant_annotations(api_headers):
|
||||
"dept": DEPT_ACRONYM,
|
||||
"civilite": "M",
|
||||
}
|
||||
etud = POST_JSON(
|
||||
etud = POST(
|
||||
"/etudiant/create",
|
||||
args,
|
||||
headers=admin_header,
|
||||
@ -304,7 +304,7 @@ def test_etudiant_annotations(api_headers):
|
||||
assert etud["nom"]
|
||||
assert etud["annotations"] == []
|
||||
# ajoute annotation
|
||||
annotation = POST_JSON(
|
||||
annotation = POST(
|
||||
f"/etudiant/etudid/{etudid}/annotation",
|
||||
{"comment": "annotation 1"},
|
||||
headers=admin_header,
|
||||
@ -318,7 +318,7 @@ def test_etudiant_annotations(api_headers):
|
||||
assert etud["annotations"][0]["comment"] == "annotation 1"
|
||||
assert etud["annotations"][0]["id"] == annotation_id
|
||||
# Supprime annotation
|
||||
POST_JSON(
|
||||
POST(
|
||||
f"/etudiant/etudid/{etudid}/annotation/{annotation_id}/delete",
|
||||
headers=admin_header,
|
||||
)
|
||||
@ -932,7 +932,7 @@ def test_etudiant_bulletin_semestre(api_headers):
|
||||
|
||||
### -------- Modifie publication bulletins
|
||||
admin_header = get_auth_headers(API_USER_ADMIN, API_PASSWORD_ADMIN)
|
||||
formsemestre = POST_JSON(
|
||||
formsemestre = POST(
|
||||
f"/formsemestre/{1}/edit", {"bul_hide_xml": True}, headers=admin_header
|
||||
)
|
||||
assert formsemestre["bul_hide_xml"] is True
|
||||
@ -943,7 +943,7 @@ def test_etudiant_bulletin_semestre(api_headers):
|
||||
# /ScoDoc/api/etudiant/nip/12345/formsemestre/123/bulletin/long/pdf/nosi
|
||||
# TODO voir forme utilisée par ScoDoc en interne:
|
||||
# formsemestre_bulletinetud?formsemestre_id=1263&etudid=16387
|
||||
formsemestre = POST_JSON(
|
||||
formsemestre = POST(
|
||||
f"/formsemestre/{1}/edit", {"bul_hide_xml": False}, headers=admin_header
|
||||
)
|
||||
|
||||
@ -1000,7 +1000,7 @@ def test_etudiant_create(api_headers):
|
||||
}
|
||||
],
|
||||
}
|
||||
etud = POST_JSON(
|
||||
etud = POST(
|
||||
"/etudiant/create",
|
||||
args,
|
||||
headers=admin_header,
|
||||
@ -1023,7 +1023,7 @@ def test_etudiant_create(api_headers):
|
||||
# assert etud["adresses"][0]["emailperso"] == args["adresses"][0]["emailperso"]
|
||||
|
||||
# Edition
|
||||
etud = POST_JSON(
|
||||
etud = POST(
|
||||
f"/etudiant/etudid/{etudid}/edit",
|
||||
{
|
||||
"civilite": "F",
|
||||
@ -1039,7 +1039,7 @@ def test_etudiant_create(api_headers):
|
||||
assert len(etud["adresses"]) == 1
|
||||
# assert etud["adresses"][0]["villedomicile"] == args["adresses"][0]["villedomicile"]
|
||||
# assert etud["adresses"][0]["emailperso"] == args["adresses"][0]["emailperso"]
|
||||
etud = POST_JSON(
|
||||
etud = POST(
|
||||
f"/etudiant/etudid/{etudid}/edit",
|
||||
{
|
||||
"adresses": [
|
||||
@ -1051,7 +1051,7 @@ def test_etudiant_create(api_headers):
|
||||
headers=admin_header,
|
||||
)
|
||||
assert etud["adresses"][0]["villedomicile"] == "Barcelona"
|
||||
etud = POST_JSON(
|
||||
etud = POST(
|
||||
f"/etudiant/etudid/{etudid}/edit",
|
||||
{
|
||||
"admission": {
|
||||
|
@ -25,7 +25,7 @@ from tests.api.setup_test_api import (
|
||||
API_URL,
|
||||
CHECK_CERTIFICATE,
|
||||
GET,
|
||||
POST_JSON,
|
||||
POST,
|
||||
api_admin_headers,
|
||||
api_headers,
|
||||
check_failure_post,
|
||||
@ -114,7 +114,7 @@ def test_evaluation_create(api_admin_headers):
|
||||
)
|
||||
nb_evals = len(evaluations)
|
||||
#
|
||||
e = POST_JSON(
|
||||
e = POST(
|
||||
f"/moduleimpl/{moduleimpl_id}/evaluation/create",
|
||||
{"description": "eval test"},
|
||||
api_admin_headers,
|
||||
@ -157,7 +157,7 @@ def test_evaluation_create(api_admin_headers):
|
||||
"publish_incomplete": True,
|
||||
"note_max": 100.0,
|
||||
}
|
||||
e = POST_JSON(
|
||||
e = POST(
|
||||
f"/moduleimpl/{moduleimpl_id}/evaluation/create",
|
||||
data,
|
||||
api_admin_headers,
|
||||
@ -209,7 +209,7 @@ def test_evaluation_create(api_admin_headers):
|
||||
ue_ids = [ue["id"] for ue in ues]
|
||||
poids = {ue_id: float(i) + 0.5 for i, ue_id in enumerate(ue_ids)}
|
||||
data.update({"description": "eval avec poids", "poids": poids})
|
||||
e = POST_JSON(
|
||||
e = POST(
|
||||
f"/moduleimpl/{moduleimpl_id}/evaluation/create",
|
||||
data,
|
||||
api_admin_headers,
|
||||
@ -223,7 +223,7 @@ def test_evaluation_create(api_admin_headers):
|
||||
assert new_nb_evals == nb_evals + 1
|
||||
nb_evals = new_nb_evals
|
||||
# Delete
|
||||
ans = POST_JSON(
|
||||
ans = POST(
|
||||
f"/evaluation/{e_ret['id']}/delete",
|
||||
headers=api_admin_headers,
|
||||
)
|
||||
|
@ -26,7 +26,7 @@ from tests.api.setup_test_api import (
|
||||
API_URL,
|
||||
CHECK_CERTIFICATE,
|
||||
GET,
|
||||
POST_JSON,
|
||||
POST,
|
||||
)
|
||||
from tests.api.tools_test_api import (
|
||||
verify_fields,
|
||||
@ -338,11 +338,11 @@ def test_api_ue_apo(api_admin_headers):
|
||||
ue_id = 1
|
||||
ue = GET(path=f"/formation/ue/{ue_id}", headers=api_admin_headers)
|
||||
assert ue["id"] == ue_id
|
||||
r = POST_JSON(
|
||||
r = POST(
|
||||
f"/formation/ue/{ue_id}/set_code_apogee/APOUE", {}, api_admin_headers, raw=True
|
||||
)
|
||||
assert r.text == "APOUE"
|
||||
r = POST_JSON(
|
||||
r = POST(
|
||||
f"/formation/ue/{ue_id}/set_code_apogee_rcue/APORCUE",
|
||||
{},
|
||||
api_admin_headers,
|
||||
@ -363,7 +363,7 @@ def test_api_module_apo(api_admin_headers):
|
||||
module = GET(path=f"/formation/module/{module_id}", headers=api_admin_headers)
|
||||
assert module["id"] == module_id
|
||||
assert module["code_apogee"] == ""
|
||||
r = POST_JSON(
|
||||
r = POST(
|
||||
f"/formation/module/{module_id}/set_code_apogee/APOMOD",
|
||||
{},
|
||||
api_admin_headers,
|
||||
@ -379,7 +379,7 @@ def test_api_module_edit(api_admin_headers):
|
||||
/formation/module/<int:module_id>/edit
|
||||
"""
|
||||
module_id = 1
|
||||
module = POST_JSON(
|
||||
module = POST(
|
||||
f"/formation/module/{module_id}/edit",
|
||||
{"heures_cours": 16, "semestre_id": 1, "abbrev": "ALLO"},
|
||||
api_admin_headers,
|
||||
@ -390,7 +390,7 @@ def test_api_module_edit(api_admin_headers):
|
||||
assert module["abbrev"] == "ALLO"
|
||||
# tente de changer l'UE: ne devrait rien faire:
|
||||
ue_id = module["ue_id"]
|
||||
module = POST_JSON(
|
||||
module = POST(
|
||||
f"/formation/module/{module_id}/edit",
|
||||
{"ue_id": 666},
|
||||
api_admin_headers,
|
||||
@ -398,7 +398,7 @@ def test_api_module_edit(api_admin_headers):
|
||||
assert module["ue_id"] == ue_id
|
||||
# tente de changer la formation: ne devrait rien faire:
|
||||
formation_id = module["formation_id"]
|
||||
module = POST_JSON(
|
||||
module = POST(
|
||||
f"/formation/module/{module_id}/edit",
|
||||
{"formation_id": 666},
|
||||
api_admin_headers,
|
||||
@ -406,7 +406,7 @@ def test_api_module_edit(api_admin_headers):
|
||||
assert module["formation_id"] == formation_id
|
||||
# -- assignation de parcours (ce test suppose que les parcours 1, 2, 3 existent)
|
||||
assert module["parcours"] == []
|
||||
module = POST_JSON(
|
||||
module = POST(
|
||||
f"/formation/module/{module_id}/edit",
|
||||
{"parcours": [1, 2, 3]},
|
||||
api_admin_headers,
|
||||
@ -419,7 +419,7 @@ def test_api_ue_edit(api_admin_headers):
|
||||
/formation/ue/<int:ue_id>/edit
|
||||
"""
|
||||
ue_id = 1
|
||||
ue = POST_JSON(
|
||||
ue = POST(
|
||||
f"/formation/ue/{ue_id}/edit",
|
||||
{"titre": "formation test modifiée", "numero": 22},
|
||||
api_admin_headers,
|
||||
@ -430,7 +430,7 @@ def test_api_ue_edit(api_admin_headers):
|
||||
|
||||
# tente de changer le niveau de compétence: ne devrait rien faire:
|
||||
niveau_competence_id = ue["niveau_competence_id"]
|
||||
ue = POST_JSON(
|
||||
ue = POST(
|
||||
f"/formation/ue/{ue_id}/edit",
|
||||
{"niveau_competence_id": 666},
|
||||
api_admin_headers,
|
||||
@ -438,7 +438,7 @@ def test_api_ue_edit(api_admin_headers):
|
||||
assert ue["niveau_competence_id"] == niveau_competence_id
|
||||
# tente de changer la formation: ne devrait rien faire:
|
||||
formation_id = ue["formation_id"]
|
||||
ue = POST_JSON(
|
||||
ue = POST(
|
||||
f"/formation/ue/{ue_id}/edit",
|
||||
{"formation_id": 666},
|
||||
api_admin_headers,
|
||||
|
@ -23,7 +23,7 @@ from tests.api.setup_test_api import (
|
||||
API_URL,
|
||||
CHECK_CERTIFICATE,
|
||||
GET,
|
||||
POST_JSON,
|
||||
POST,
|
||||
api_headers,
|
||||
)
|
||||
|
||||
@ -54,7 +54,7 @@ def test_jury_decisions(api_headers):
|
||||
# ues = export_formation["ue"]
|
||||
# # Enregistre une validation d'RCUE
|
||||
# etudid = etudiants[0]["id"]
|
||||
# validation = POST_JSON(
|
||||
# validation = POST(
|
||||
# f"/etudiant/{etudid}/jury/validation_rcue/record",
|
||||
# data={
|
||||
# "code": "ADM",
|
||||
|
@ -12,7 +12,7 @@ from tests.api.setup_test_api import (
|
||||
API_URL,
|
||||
CHECK_CERTIFICATE,
|
||||
GET,
|
||||
POST_JSON,
|
||||
POST,
|
||||
APIError,
|
||||
api_headers,
|
||||
api_admin_headers,
|
||||
@ -105,7 +105,7 @@ def check_failure_post(path, headers, data, err=None):
|
||||
APIError: Une erreur car la requête a fonctionné (mauvais comportement)
|
||||
"""
|
||||
try:
|
||||
data = POST_JSON(path=path, headers=headers, data=data)
|
||||
data = POST(path=path, headers=headers, data=data)
|
||||
# ^ Renvoi un 404
|
||||
except APIError as api_err:
|
||||
if err is not None:
|
||||
@ -204,14 +204,14 @@ def test_route_create(api_admin_headers):
|
||||
# Bon fonctionnement
|
||||
data = create_data("valide", "01")
|
||||
|
||||
res = POST_JSON(f"/justificatif/{ETUDID}/create", [data], api_admin_headers)
|
||||
res = POST(f"/justificatif/{ETUDID}/create", [data], api_admin_headers)
|
||||
check_fields(res, BATCH_FIELD)
|
||||
assert len(res["success"]) == 1
|
||||
|
||||
TO_REMOVE.append(res["success"][0]["message"]["justif_id"])
|
||||
|
||||
data2 = create_data("modifie", "02", "raison")
|
||||
res = POST_JSON(f"/justificatif/{ETUDID}/create", [data2], api_admin_headers)
|
||||
res = POST(f"/justificatif/{ETUDID}/create", [data2], api_admin_headers)
|
||||
check_fields(res, BATCH_FIELD)
|
||||
assert len(res["success"]) == 1
|
||||
|
||||
@ -220,7 +220,7 @@ def test_route_create(api_admin_headers):
|
||||
# Mauvais fonctionnement
|
||||
check_failure_post(f"/justificatif/{FAUX}/create", api_admin_headers, [data])
|
||||
|
||||
res = POST_JSON(
|
||||
res = POST(
|
||||
f"/justificatif/{ETUDID}/create",
|
||||
[create_data("absent", "03")],
|
||||
api_admin_headers,
|
||||
@ -239,7 +239,7 @@ def test_route_create(api_admin_headers):
|
||||
for d in range(randint(3, 5))
|
||||
]
|
||||
|
||||
res = POST_JSON(f"/justificatif/{ETUDID}/create", data, api_admin_headers)
|
||||
res = POST(f"/justificatif/{ETUDID}/create", data, api_admin_headers)
|
||||
check_fields(res, BATCH_FIELD)
|
||||
for dat in res["success"]:
|
||||
check_fields(dat["message"], CREATE_FIELD)
|
||||
@ -253,7 +253,7 @@ def test_route_create(api_admin_headers):
|
||||
create_data("valide", 32),
|
||||
]
|
||||
|
||||
res = POST_JSON(f"/justificatif/{ETUDID}/create", data2, api_admin_headers)
|
||||
res = POST(f"/justificatif/{ETUDID}/create", data2, api_admin_headers)
|
||||
check_fields(res, BATCH_FIELD)
|
||||
assert len(res["errors"]) == 3
|
||||
|
||||
@ -270,11 +270,11 @@ def test_route_edit(api_admin_headers):
|
||||
# Bon fonctionnement
|
||||
|
||||
data = {"etat": "modifie", "raison": "test"}
|
||||
res = POST_JSON(f"/justificatif/{TO_REMOVE[0]}/edit", data, api_admin_headers)
|
||||
res = POST(f"/justificatif/{TO_REMOVE[0]}/edit", data, api_admin_headers)
|
||||
assert isinstance(res, dict) and "couverture" in res.keys()
|
||||
|
||||
data["raison"] = None
|
||||
res = POST_JSON(f"/justificatif/{TO_REMOVE[1]}/edit", data, api_admin_headers)
|
||||
res = POST(f"/justificatif/{TO_REMOVE[1]}/edit", data, api_admin_headers)
|
||||
assert isinstance(res, dict) and "couverture" in res.keys()
|
||||
|
||||
# Mauvais fonctionnement
|
||||
@ -296,13 +296,13 @@ def test_route_delete(api_admin_headers):
|
||||
# Bon fonctionnement
|
||||
data = TO_REMOVE[0]
|
||||
|
||||
res = POST_JSON("/justificatif/delete", [data], api_admin_headers)
|
||||
res = POST("/justificatif/delete", [data], api_admin_headers)
|
||||
check_fields(res, BATCH_FIELD)
|
||||
for dat in res["success"]:
|
||||
assert dat["message"] == "OK"
|
||||
|
||||
# Mauvais fonctionnement
|
||||
res = POST_JSON("/justificatif/delete", [data], api_admin_headers)
|
||||
res = POST("/justificatif/delete", [data], api_admin_headers)
|
||||
check_fields(res, BATCH_FIELD)
|
||||
assert len(res["errors"]) == 1
|
||||
|
||||
@ -312,7 +312,7 @@ def test_route_delete(api_admin_headers):
|
||||
|
||||
data = TO_REMOVE[1:]
|
||||
|
||||
res = POST_JSON("/justificatif/delete", data, api_admin_headers)
|
||||
res = POST("/justificatif/delete", data, api_admin_headers)
|
||||
check_fields(res, BATCH_FIELD)
|
||||
for dat in res["success"]:
|
||||
assert dat["message"] == "OK"
|
||||
@ -325,7 +325,7 @@ def test_route_delete(api_admin_headers):
|
||||
FAUX + 2,
|
||||
]
|
||||
|
||||
res = POST_JSON("/justificatif/delete", data2, api_admin_headers)
|
||||
res = POST("/justificatif/delete", data2, api_admin_headers)
|
||||
check_fields(res, BATCH_FIELD)
|
||||
assert len(res["errors"]) == 3
|
||||
|
||||
@ -473,15 +473,13 @@ def test_remove_justificatif(api_admin_headers):
|
||||
filename: str = "tests/api/test_api_justificatif2.txt"
|
||||
_send_file(2, filename, api_admin_headers)
|
||||
|
||||
res: dict = POST_JSON(
|
||||
"/justificatif/1/remove", {"remove": "all"}, api_admin_headers
|
||||
)
|
||||
res: dict = POST("/justificatif/1/remove", {"remove": "all"}, api_admin_headers)
|
||||
assert res == {"response": "removed"}
|
||||
l = GET("/justificatif/1/list", api_admin_headers)
|
||||
assert isinstance(l, dict)
|
||||
assert l["total"] == 0
|
||||
|
||||
res: dict = POST_JSON(
|
||||
res: dict = POST(
|
||||
"/justificatif/2/remove",
|
||||
{"remove": "list", "filenames": ["test_api_justificatif2.txt"]},
|
||||
api_admin_headers,
|
||||
@ -491,7 +489,7 @@ def test_remove_justificatif(api_admin_headers):
|
||||
assert isinstance(l, dict)
|
||||
assert l["total"] == 1
|
||||
|
||||
res: dict = POST_JSON(
|
||||
res: dict = POST(
|
||||
"/justificatif/2/remove",
|
||||
{"remove": "list", "filenames": ["test_api_justificatif.txt"]},
|
||||
api_admin_headers,
|
||||
|
@ -21,7 +21,7 @@ from tests.api.setup_test_api import (
|
||||
API_URL,
|
||||
CHECK_CERTIFICATE,
|
||||
GET,
|
||||
POST_JSON,
|
||||
POST,
|
||||
api_headers,
|
||||
)
|
||||
from tests.api.tools_test_api import (
|
||||
@ -65,15 +65,15 @@ def test_formsemestre_partition(api_headers):
|
||||
headers = api_headers
|
||||
formsemestre_id = 1
|
||||
partitions = GET(f"/formsemestre/{formsemestre_id}/partitions", headers=headers)
|
||||
# au départ, pas de partitions
|
||||
assert partitions == {}
|
||||
# au départ, une partition (TD avec groupes A et B)
|
||||
assert len(partitions) == 1
|
||||
# --- Création partition
|
||||
partition_d = {
|
||||
"partition_name": "T&Dé",
|
||||
"bul_show_rank": True,
|
||||
"show_in_lists": True,
|
||||
}
|
||||
partition_r = POST_JSON(
|
||||
partition_r = POST(
|
||||
f"/formsemestre/{formsemestre_id}/partition/create",
|
||||
partition_d,
|
||||
headers=headers,
|
||||
@ -83,7 +83,7 @@ def test_formsemestre_partition(api_headers):
|
||||
assert partition_r["groups"] == {}
|
||||
# --- Création Groupe
|
||||
group_d = {"group_name": "Aé-&"}
|
||||
group_r = POST_JSON(
|
||||
group_r = POST(
|
||||
f"/partition/{partition_r['id']}/group/create",
|
||||
group_d,
|
||||
headers=headers,
|
||||
@ -102,14 +102,14 @@ def test_formsemestre_partition(api_headers):
|
||||
|
||||
# --- Ajout d'un groupe avec edt_id
|
||||
group_d = {"group_name": "extra", "edt_id": "GEDT"}
|
||||
group_r = POST_JSON(
|
||||
group_r = POST(
|
||||
f"/partition/{partition_r['id']}/group/create",
|
||||
group_d,
|
||||
headers=headers,
|
||||
)
|
||||
assert group_r["edt_id"] == "GEDT"
|
||||
# Edit edt_id
|
||||
group_r = POST_JSON(
|
||||
group_r = POST(
|
||||
f"/group/{group_r['id']}/edit",
|
||||
{"edt_id": "GEDT2"},
|
||||
headers=headers,
|
||||
@ -121,7 +121,7 @@ def test_formsemestre_partition(api_headers):
|
||||
assert group["edt_id"] == "GEDT2"
|
||||
|
||||
# Change edt_id via route dédiée:
|
||||
group_t = POST_JSON(
|
||||
group_t = POST(
|
||||
f"/group/{group_r['id']}/set_edt_id/GEDT3",
|
||||
headers=headers,
|
||||
)
|
||||
@ -130,7 +130,7 @@ def test_formsemestre_partition(api_headers):
|
||||
|
||||
# Place un étudiant dans le groupe
|
||||
etud = GET(f"/formsemestre/{formsemestre_id}/etudiants", headers=headers)[0]
|
||||
repl = POST_JSON(f"/group/{group['id']}/set_etudiant/{etud['id']}", headers=headers)
|
||||
repl = POST(f"/group/{group['id']}/set_etudiant/{etud['id']}", headers=headers)
|
||||
assert isinstance(repl, dict)
|
||||
assert repl["group_id"] == group["id"]
|
||||
assert repl["etudid"] == etud["id"]
|
||||
@ -139,21 +139,17 @@ def test_formsemestre_partition(api_headers):
|
||||
assert len(etuds) == 1
|
||||
assert etuds[0]["id"] == etud["id"]
|
||||
# Retire l'étudiant du groupe
|
||||
repl = POST_JSON(
|
||||
f"/group/{group['id']}/remove_etudiant/{etud['id']}", headers=headers
|
||||
)
|
||||
repl = POST(f"/group/{group['id']}/remove_etudiant/{etud['id']}", headers=headers)
|
||||
assert len(GET(f"/group/{group['id']}/etudiants", headers=headers)) == 0
|
||||
|
||||
# Le retire à nouveau ! (bug #465)
|
||||
repl = POST_JSON(
|
||||
f"/group/{group['id']}/remove_etudiant/{etud['id']}", headers=headers
|
||||
)
|
||||
repl = POST(f"/group/{group['id']}/remove_etudiant/{etud['id']}", headers=headers)
|
||||
assert repl["group_id"] == group["id"]
|
||||
# Avec partition (vérifie encodeur JSON)
|
||||
partitions = GET(f"/formsemestre/{formsemestre_id}/partitions", headers=headers)
|
||||
assert partitions
|
||||
# Delete partition
|
||||
repl = POST_JSON(f"/partition/{partition_r['id']}/delete", headers=headers)
|
||||
repl = POST(f"/partition/{partition_r['id']}/delete", headers=headers)
|
||||
assert repl["OK"] is True
|
||||
|
||||
|
||||
|
@ -11,7 +11,7 @@ from tests.api.setup_test_api import (
|
||||
APIError,
|
||||
CHECK_CERTIFICATE,
|
||||
GET,
|
||||
POST_JSON,
|
||||
POST,
|
||||
api_headers,
|
||||
api_admin_headers,
|
||||
get_auth_headers,
|
||||
@ -76,7 +76,7 @@ def test_edit_users(api_admin_headers):
|
||||
"""
|
||||
admin_h = api_admin_headers
|
||||
nb_users = len(GET("/users/query", headers=admin_h))
|
||||
user = POST_JSON(
|
||||
user = POST(
|
||||
"/user/create",
|
||||
{"user_name": "test_edit_users", "nom": "Toto"},
|
||||
headers=admin_h,
|
||||
@ -86,7 +86,7 @@ def test_edit_users(api_admin_headers):
|
||||
assert user["active"] is True
|
||||
assert (nb_users + 1) == len(GET("/users/query", headers=admin_h))
|
||||
# Change le dept et rend inactif
|
||||
user = POST_JSON(
|
||||
user = POST(
|
||||
f"/user/{user['id']}/edit",
|
||||
{"active": False, "dept": "TAPI", "edt_id": "GGG"},
|
||||
headers=admin_h,
|
||||
@ -107,37 +107,37 @@ def test_roles(api_admin_headers):
|
||||
/user/<int:uid>/edit
|
||||
"""
|
||||
admin_h = api_admin_headers
|
||||
user = POST_JSON(
|
||||
user = POST(
|
||||
"/user/create",
|
||||
{"user_name": "test_roles", "nom": "Role", "prenom": "Test"},
|
||||
headers=admin_h,
|
||||
)
|
||||
uid = user["id"]
|
||||
ans = POST_JSON(f"/user/{uid}/role/Secr/add", headers=admin_h)
|
||||
ans = POST(f"/user/{uid}/role/Secr/add", headers=admin_h)
|
||||
assert ans["user_name"] == "test_roles"
|
||||
role = POST_JSON("/role/create/Test_X", headers=admin_h)
|
||||
role = POST("/role/create/Test_X", headers=admin_h)
|
||||
assert role["role_name"] == "Test_X"
|
||||
assert role["permissions"] == []
|
||||
role = GET("/role/Test_X", headers=admin_h)
|
||||
assert role["role_name"] == "Test_X"
|
||||
assert role["permissions"] == []
|
||||
role = POST_JSON("/role/Test_X/edit", {"role_name": "Test_Y"}, headers=admin_h)
|
||||
role = POST("/role/Test_X/edit", {"role_name": "Test_Y"}, headers=admin_h)
|
||||
assert role["role_name"] == "Test_Y"
|
||||
role = GET("/role/Test_Y", headers=admin_h)
|
||||
assert role["role_name"] == "Test_Y"
|
||||
role = POST_JSON(
|
||||
role = POST(
|
||||
"/role/Test_Y/edit",
|
||||
{"permissions": ["ScoView", "AbsChange"]},
|
||||
headers=admin_h,
|
||||
)
|
||||
assert set(role["permissions"]) == {"ScoView", "AbsChange"}
|
||||
role = POST_JSON("/role/Test_Y/add_permission/AbsAddBillet", headers=admin_h)
|
||||
role = POST("/role/Test_Y/add_permission/AbsAddBillet", headers=admin_h)
|
||||
assert set(role["permissions"]) == {"ScoView", "AbsChange", "AbsAddBillet"}
|
||||
role = GET("/role/Test_Y", headers=admin_h)
|
||||
assert set(role["permissions"]) == {"ScoView", "AbsChange", "AbsAddBillet"}
|
||||
role = POST_JSON("/role/Test_Y/remove_permission/AbsChange", headers=admin_h)
|
||||
role = POST("/role/Test_Y/remove_permission/AbsChange", headers=admin_h)
|
||||
assert set(role["permissions"]) == {"ScoView", "AbsAddBillet"}
|
||||
ans = POST_JSON("/role/Test_Y/delete", headers=admin_h)
|
||||
ans = POST("/role/Test_Y/delete", headers=admin_h)
|
||||
assert ans["OK"] is True
|
||||
|
||||
|
||||
@ -153,7 +153,7 @@ def test_modif_users_depts(api_admin_headers):
|
||||
# On va utiliser les 3 1er dept (TAPI, AA, BB)
|
||||
# On crée un nouvel utilisateur "chef2", admin dans les 2 premiers dept
|
||||
# puis un utilisateur lambda, dans le dept 2 (AA)
|
||||
chef2 = POST_JSON(
|
||||
chef2 = POST(
|
||||
"/user/create",
|
||||
{
|
||||
"user_name": "chef2",
|
||||
@ -163,23 +163,23 @@ def test_modif_users_depts(api_admin_headers):
|
||||
},
|
||||
headers=admin_h,
|
||||
)
|
||||
role_chef = POST_JSON(
|
||||
role_chef = POST(
|
||||
"/role/create/chef",
|
||||
{"permissions": ["ScoView", "UsersAdmin", "UsersView"]},
|
||||
headers=admin_h,
|
||||
)
|
||||
_ = POST_JSON(
|
||||
_ = POST(
|
||||
f"/user/{chef2['id']}/role/chef/add/departement/{dept1['acronym']}",
|
||||
headers=admin_h,
|
||||
)
|
||||
_ = POST_JSON(
|
||||
_ = POST(
|
||||
f"/user/{chef2['id']}/role/chef/add/departement/{dept2['acronym']}",
|
||||
headers=admin_h,
|
||||
)
|
||||
# Un mot de passe trop simple:
|
||||
ok = False
|
||||
try:
|
||||
_ = POST_JSON(
|
||||
_ = POST(
|
||||
f"/user/{chef2['id']}/password",
|
||||
{"password": "123456"},
|
||||
headers=admin_h,
|
||||
@ -190,13 +190,13 @@ def test_modif_users_depts(api_admin_headers):
|
||||
assert ok
|
||||
# Un "vrai" mot de passe:
|
||||
chef2_password = "17HIOPpYhabb8qw'E:/jd7FFddjd"
|
||||
_ = POST_JSON(
|
||||
_ = POST(
|
||||
f"/user/{chef2['id']}/password",
|
||||
{"password": chef2_password},
|
||||
headers=admin_h,
|
||||
)
|
||||
# Création user lambda:
|
||||
u_lambda = POST_JSON(
|
||||
u_lambda = POST(
|
||||
"/user/create",
|
||||
{
|
||||
"user_name": "lambda",
|
||||
@ -210,7 +210,7 @@ def test_modif_users_depts(api_admin_headers):
|
||||
# Le chef va modifier u_lambda:
|
||||
chef_h = get_auth_headers(chef2["user_name"], chef2_password)
|
||||
# on utilise une URL avec département car on n'a pas le droit sur tous:
|
||||
u = POST_JSON(
|
||||
u = POST(
|
||||
f"/user/{u_lambda['id']}/edit",
|
||||
{"nom": "toto"},
|
||||
headers=chef_h,
|
||||
@ -218,7 +218,7 @@ def test_modif_users_depts(api_admin_headers):
|
||||
)
|
||||
assert u["nom"] == "toto"
|
||||
# Dans l'autre ?
|
||||
u = POST_JSON(
|
||||
u = POST(
|
||||
f"/user/{u_lambda['id']}/edit",
|
||||
{"nom": "toto"},
|
||||
headers=chef_h,
|
||||
@ -227,7 +227,7 @@ def test_modif_users_depts(api_admin_headers):
|
||||
# mais pas dans le troisième:
|
||||
ok = False
|
||||
try:
|
||||
u = POST_JSON(
|
||||
u = POST(
|
||||
f"/user/{u_lambda['id']}/edit",
|
||||
{"nom": "toto"},
|
||||
headers=chef_h,
|
||||
@ -240,7 +240,7 @@ def test_modif_users_depts(api_admin_headers):
|
||||
# Nettoyage:
|
||||
# on ne peut pas supprimer l'utilisateur lambda, mais on
|
||||
# le rend inactif et on le retire de son département
|
||||
u = POST_JSON(
|
||||
u = POST(
|
||||
f"/user/{u_lambda['id']}/edit",
|
||||
{"active": False, "dept": None},
|
||||
headers=admin_h,
|
||||
|
@ -1,5 +1,6 @@
|
||||
import pytest
|
||||
|
||||
import flask
|
||||
from flask import g
|
||||
from flask_login import login_user
|
||||
|
||||
|
@ -50,8 +50,9 @@
|
||||
"moy_sae_14_3": "17.83",
|
||||
"moy_sae_15_3": "~",
|
||||
"ues_validables": "3/3",
|
||||
"nbabs": 0,
|
||||
"nbabs": 1,
|
||||
"nbabsjust": 0,
|
||||
"part_2": "",
|
||||
"code_cursus": "S1",
|
||||
"bac": "",
|
||||
"specialite": "",
|
||||
@ -109,8 +110,9 @@
|
||||
"moy_sae_14_3": "10.74",
|
||||
"moy_sae_15_3": "~",
|
||||
"ues_validables": "3/3",
|
||||
"nbabs": 2,
|
||||
"nbabs": 0,
|
||||
"nbabsjust": 0,
|
||||
"part_2": "",
|
||||
"code_cursus": "S1",
|
||||
"bac": "",
|
||||
"specialite": "",
|
||||
@ -170,6 +172,7 @@
|
||||
"ues_validables": "2/3",
|
||||
"nbabs": 0,
|
||||
"nbabsjust": 0,
|
||||
"part_2": "",
|
||||
"code_cursus": "S1",
|
||||
"bac": "",
|
||||
"specialite": "",
|
||||
@ -229,6 +232,7 @@
|
||||
"ues_validables": "2/3",
|
||||
"nbabs": 0,
|
||||
"nbabsjust": 0,
|
||||
"part_2": "",
|
||||
"code_cursus": "S1",
|
||||
"bac": "",
|
||||
"specialite": "",
|
||||
@ -286,8 +290,9 @@
|
||||
"moy_sae_14_3": "11.09",
|
||||
"moy_sae_15_3": "~",
|
||||
"ues_validables": "1/3",
|
||||
"nbabs": 3,
|
||||
"nbabs": 0,
|
||||
"nbabsjust": 0,
|
||||
"part_2": "",
|
||||
"code_cursus": "S1",
|
||||
"bac": "",
|
||||
"specialite": "",
|
||||
@ -347,6 +352,7 @@
|
||||
"ues_validables": "2/3",
|
||||
"nbabs": 0,
|
||||
"nbabsjust": 0,
|
||||
"part_2": "",
|
||||
"code_cursus": "S1",
|
||||
"bac": "",
|
||||
"specialite": "",
|
||||
@ -404,8 +410,9 @@
|
||||
"moy_sae_14_3": "05.17",
|
||||
"moy_sae_15_3": "~",
|
||||
"ues_validables": "1/3",
|
||||
"nbabs": 3,
|
||||
"nbabs": 0,
|
||||
"nbabsjust": 0,
|
||||
"part_2": "",
|
||||
"code_cursus": "S1",
|
||||
"bac": "",
|
||||
"specialite": "",
|
||||
@ -465,6 +472,7 @@
|
||||
"ues_validables": "0/3",
|
||||
"nbabs": 0,
|
||||
"nbabsjust": 0,
|
||||
"part_2": "",
|
||||
"code_cursus": "S1",
|
||||
"bac": "",
|
||||
"specialite": "",
|
||||
@ -524,6 +532,7 @@
|
||||
"ues_validables": "0/3",
|
||||
"nbabs": 0,
|
||||
"nbabsjust": 0,
|
||||
"part_2": "",
|
||||
"code_cursus": "S1",
|
||||
"bac": "",
|
||||
"specialite": "",
|
||||
@ -583,6 +592,7 @@
|
||||
"ues_validables": "0/3",
|
||||
"nbabs": 0,
|
||||
"nbabsjust": 0,
|
||||
"part_2": "",
|
||||
"code_cursus": "S1",
|
||||
"bac": "",
|
||||
"specialite": "",
|
||||
@ -642,6 +652,7 @@
|
||||
"ues_validables": "",
|
||||
"nbabs": 2,
|
||||
"nbabsjust": 0,
|
||||
"part_2": "D\u00e9f.",
|
||||
"code_cursus": "S1",
|
||||
"bac": "",
|
||||
"specialite": "",
|
||||
@ -699,8 +710,9 @@
|
||||
"moy_sae_14_3": "",
|
||||
"moy_sae_15_3": "",
|
||||
"ues_validables": "",
|
||||
"nbabs": 2,
|
||||
"nbabs": 0,
|
||||
"nbabsjust": 0,
|
||||
"part_2": "D\u00e9m.",
|
||||
"code_cursus": "S1",
|
||||
"bac": "",
|
||||
"specialite": "",
|
||||
@ -758,8 +770,9 @@
|
||||
"moy_sae_14_3": "",
|
||||
"moy_sae_15_3": "",
|
||||
"ues_validables": "",
|
||||
"nbabs": 0,
|
||||
"nbabs": 2,
|
||||
"nbabsjust": 0,
|
||||
"part_2": "D\u00e9f.",
|
||||
"code_cursus": "S1",
|
||||
"bac": "",
|
||||
"specialite": "",
|
||||
@ -817,8 +830,9 @@
|
||||
"moy_sae_14_3": "",
|
||||
"moy_sae_15_3": "",
|
||||
"ues_validables": "",
|
||||
"nbabs": 1,
|
||||
"nbabs": 0,
|
||||
"nbabsjust": 0,
|
||||
"part_2": "D\u00e9m.",
|
||||
"code_cursus": "S1",
|
||||
"bac": "",
|
||||
"specialite": "",
|
||||
@ -878,6 +892,7 @@
|
||||
"ues_validables": "",
|
||||
"nbabs": 0,
|
||||
"nbabsjust": 0,
|
||||
"part_2": "D\u00e9f.",
|
||||
"code_cursus": "S1",
|
||||
"bac": "",
|
||||
"specialite": "",
|
||||
@ -935,8 +950,9 @@
|
||||
"moy_sae_14_3": "",
|
||||
"moy_sae_15_3": "",
|
||||
"ues_validables": "",
|
||||
"nbabs": 0,
|
||||
"nbabs": 1,
|
||||
"nbabsjust": 0,
|
||||
"part_2": "D\u00e9m.",
|
||||
"code_cursus": "S1",
|
||||
"bac": "",
|
||||
"specialite": "",
|
||||
|
@ -1,36 +1,38 @@
|
||||
"entry_name";"url";"permission";"method";"content"
|
||||
"assiduite";"/assiduite/1";"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";
|
||||
"assiduite_justificatifs";"/assiduite/1/justificatifs";"ScoView";"GET";
|
||||
"assiduite_justificatifs";"/assiduite/1/justificatifs/long";"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?split";"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/query?etat=retard";"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/query?etat=retard";"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""}]"
|
||||
"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";"{""moduleimpl_id"":2}"
|
||||
"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";
|
||||
"justificatifs";"/justificatifs/1";"ScoView";"GET";
|
||||
"justificatifs";"/justificatifs/1/query?etat=attente";"ScoView";"GET";
|
||||
"justificatifs_dept";"/justificatifs/dept/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""}]"
|
||||
"justificatif_edit";"/justificatif/1/edit";"UsersAdmin";"POST";"{""etat"":""valide""}"
|
||||
"justificatif_edit";"/justificatif/1/edit";"UsersAdmin";"POST";"{""raison"":""MEDIC""}"
|
||||
"justificatif_delete";"/justificatif/delete";"UsersAdmin";"POST";"[2,2,3]"
|
||||
"justif_edit";"/justificatif/1/edit";"UsersAdmin";"POST";"{""etat"":""valide""}"
|
||||
"justif_edit";"/justificatif/1/edit";"UsersAdmin";"POST";"{""raison"":""MEDIC""}"
|
||||
"justif_delete";"/justificatif/delete";"UsersAdmin";"POST";"[2, 2, 3]"
|
||||
"justif_list";"/justificatif/1/list";"ScoView";"GET";
|
||||
"justif_justifies";"/justificatif/1/justifies";"UsersAdmin";"GET";
|
|
@ -1,120 +1,90 @@
|
||||
"entry_name";"url";"permission";"method";"content"
|
||||
"assiduite_create";"/assiduite/1/create";"ScoView";"POST";"{""date_debut"": ""2022-10-27T08:00"",""date_fin"": ""2022-10-27T10:00"",""etat"": ""absent""}"
|
||||
"assiduite_create";"/assiduite/1/create/batch";"ScoView";"POST";"{""batch"":[{""date_debut"": ""2022-10-27T08:00"",""date_fin"": ""2022-10-27T10:00"",""etat"": ""absent""},{""date_debut"": ""2022-10-27T08:00"",""date_fin"": ""2022-10-27T10:00"",""etat"": ""retard""},{""date_debut"": ""2022-10-27T11:00"",""date_fin"": ""2022-10-27T13:00"",""etat"": ""present""}]}"
|
||||
"assiduite_delete";"/assiduite/delete";"ScoView";"POST";"{""assiduite_id"": 1}"
|
||||
"assiduite_delete";"/assiduite/delete/batch";"ScoView";"POST";"{""batch"":[2,2,3]}"
|
||||
"assiduite_edit";"/assiduite/1/edit";"ScoView";"POST";"{""etat"": ""retard"",""moduleimpl_id"":3}"
|
||||
"assiduite_edit";"/assiduite/1/edit";"ScoView";"POST";"{""etat"":""absent""}"
|
||||
"assiduite_edit";"/assiduite/1/edit";"ScoView";"POST";"{""moduleimpl_id"":2}"
|
||||
"assiduite";"/assiduite/1";"ScoView";"GET";
|
||||
"assiduite_justificatifs";"/assiduite/1/justificatifs";"ScoView";"GET";
|
||||
"assiduite_justificatifs";"/assiduite/1/justificatifs/long";"ScoView";"GET";
|
||||
"assiduites_count";"/assiduites/1/count";"ScoView";"GET";
|
||||
"assiduites_count";"/assiduites/1/count/query?etat=present,retard&metric=compte,heure";"ScoView";"GET";
|
||||
"assiduites_count";"/assiduites/1/count/query?etat=retard";"ScoView";"GET";
|
||||
"assiduites_formsemestre_count";"/assiduites/formsemestre/1/count";"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=retard";"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?moduleimpl_id=1";"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";"/assiduites/1";"ScoView";"GET";
|
||||
"assiduites";"/assiduites/1/query?etat=retard";"ScoView";"GET";
|
||||
"assiduites";"/assiduites/1/query?moduleimpl_id=1";"ScoView";"GET";
|
||||
"departement-create";"/departement/create";"ScoSuperAdmin";"POST";"{""acronym"": ""NEWONE"" , ""visible"": true}"
|
||||
"departement-delete";"/departement/NEWONE/delete";"ScoSuperAdmin";"POST";
|
||||
"departement-edit";"/departement/NEWONE/edit";"ScoSuperAdmin";"POST";"{""visible"": false}"
|
||||
"departement-etudiants";"/departement/id/1/etudiants";"ScoView";"GET";
|
||||
"departement-etudiants";"/departement/TAPI/etudiants";"ScoView";"GET";
|
||||
"departement-formsemestres_ids";"/departement/id/1/formsemestres_ids";"ScoView";"GET";
|
||||
"departement-formsemestres_ids";"/departement/TAPI/formsemestres_ids";"ScoView";"GET";
|
||||
"departement-formsemestres-courants";"/departement/id/1/formsemestres_courants";"ScoView";"GET";
|
||||
"departement-formsemestres-courants";"/departement/TAPI/formsemestres_courants";"ScoView";"GET";
|
||||
"departement-logo";"/departement/id/1/logo/D";"ScoSuperAdmin";"GET";
|
||||
"departement-logo";"/departement/TAPI/logo/D";"ScoSuperAdmin";"GET";
|
||||
"departement-logos";"/departement/id/1/logos";"ScoSuperAdmin";"GET";
|
||||
"departement-logos";"/departement/TAPI/logos";"ScoSuperAdmin";"GET";
|
||||
"departement";"/departement/id/1";"ScoView";"GET";
|
||||
"departement";"/departement/TAPI";"ScoView";"GET";
|
||||
"departements-ids";"/departements_ids";"ScoView";"GET";
|
||||
"departements";"/departements";"ScoView";"GET";
|
||||
"etudiant_formsemestres";"/etudiant/nip/11/formsemestres";"ScoView";"GET";
|
||||
"etudiant-formsemestre-bulletin";"/etudiant/etudid/11/formsemestre/1/bulletin";"ScoView";"GET";
|
||||
#"etudiant-formsemestre-bulletin";"/etudiant/ine/INE11/formsemestre/1/bulletin";"ScoView";"GET";
|
||||
#"etudiant-formsemestre-bulletin";"/etudiant/nip/11/formsemestre/1/bulletin";"ScoView";"GET";
|
||||
#"etudiant-formsemestre-bulletin";"/etudiant/nip/11/formsemestre/1/bulletin/short/pdf";"ScoView";"GET";
|
||||
"etudiant-formsemestre-groups";"/etudiant/etudid/11/formsemestre/1/groups";"ScoView";"GET";
|
||||
"etudiant-formsemestres";"/etudiant/etudid/11/formsemestres";"ScoView";"GET";
|
||||
"etudiant-formsemestres";"/etudiant/ine/INE11/formsemestres";"ScoView";"GET";
|
||||
"etudiant";"/etudiant/etudid/11";"ScoView";"GET";
|
||||
"etudiant";"/etudiant/ine/INE11";"ScoView";"GET";
|
||||
"etudiant";"/etudiant/nip/11";"ScoView";"GET";
|
||||
"etudiants-clef";"/etudiants/etudid/11";"ScoView";"GET";
|
||||
"etudiants-clef";"/etudiants/ine/INE11";"ScoView";"GET";
|
||||
"etudiants-clef";"/etudiants/nip/11";"ScoView";"GET";
|
||||
"etudiants-courants";"/etudiants/courants?date_courante=2022-07-20";"ScoView";"GET";
|
||||
"etudiants-courants";"/etudiants/courants/long?date_courante=2022-07-20";"ScoView";"GET";
|
||||
"evaluation";"/evaluation/1";"ScoView";"GET";
|
||||
"evaluation-notes";"/evaluation/1/notes";"ScoView";"GET";
|
||||
"formation-export";"/formation/1/export_with_ids";"ScoView";"GET";
|
||||
"formation-export";"/formation/1/export";"ScoView";"GET";
|
||||
"formation-referentiel_competences";"/formation/1/referentiel_competences";"ScoView";"GET";
|
||||
"formation";"/formation/1";"ScoView";"GET";
|
||||
"formations_ids";"/formations_ids";"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/query?etat=retard";"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/query?etat=retard";"ScoView";"GET";
|
||||
"assiduites_formsemestre_count";"/assiduites/formsemestre/1/count/query?etat=present,retard&metric=compte,heure";"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""}]"
|
||||
"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";"{""moduleimpl_id"":2}"
|
||||
"assiduite_edit";"/assiduite/1/edit";"UsersAdmin";"POST";"{""etat"": ""retard"",""moduleimpl_id"":3}"
|
||||
"billets_absence_create";"/billets_absence/create";"UsersAdmin";"POST";"{""etudid"":""1"",""abs_begin"":""2023-10-27T10:00"",""abs_end"":""2023-10-28T10:00"",""description"":""grave malade"",""justified"":""1""}"
|
||||
"departements_list";"/departements";"ScoView";"GET";
|
||||
"departements_ids";"/departements_ids";"ScoView";"GET";
|
||||
"departement_by_acronym";"/departement/TAPI";"ScoView";"GET";
|
||||
"departement_get";"/departement/id/1";"ScoView";"GET";
|
||||
"departement_create";"/departement/create";"UsersAdmin";"POST";"{""acronym"":""MYDEPT"",""visible"":""1""}"
|
||||
"departement_etudiants";"/departement/TAPI/etudiants";"ScoView";"GET";
|
||||
"departement_formsemestres_ids";"/departement/TAPI/formsemestres_ids";"ScoView";"GET";
|
||||
"departement_formsemestres_ids_by_id";"/departement/id/1/formsemestres_ids";"ScoView";"GET";
|
||||
"departement_formsemestres_courants";"/departement/id/1/formsemestres_courants?date_courante=2022-01-01";"ScoView";"GET";
|
||||
"etudiants_courants";"/etudiants/courants?date_courante=2022-05-01";"ScoView";"GET";
|
||||
"etudiants_courants";"/etudiants/courants/long?date_courante=2022-05-01";"ScoView";"GET";
|
||||
"bulletin";"/etudiant/etudid/1/formsemestre/1/bulletin";"ScoView";"GET";
|
||||
"etudiant_groups";"/etudiant/etudid/1/formsemestre/1/groups";"ScoView";"GET";
|
||||
"etudiant_edit";"/etudiant/ine/INE1/edit";"UsersAdmin";"POST";"{""prenom"":""Nouveau Prénom"", ""adresses"":[{""email"":""nouvelle@adresse.fr""}]}"
|
||||
"etudiant_annotation";"/etudiant/etudid/1/annotation";"UsersAdmin";"POST";"{""comment"":""une annotation sur l'étudiant""}"
|
||||
"moduleimpl_evaluations";"/moduleimpl/1/evaluations";"ScoView";"GET";
|
||||
"evaluation_notes";"/evaluation/2/notes";"ScoView";"GET";
|
||||
"evaluation_set_notes";"/evaluation/1/notes/set";"UsersAdmin";"POST";"{""notes"": [[1, 17], [2, ""SUPR""]], ""comment"" : ""sample test""}"
|
||||
"evaluation_create";"/moduleimpl/1/evaluation/create";"UsersAdmin";"POST";"{""description"":""Exemple éval.""}"
|
||||
"formations";"/formations";"ScoView";"GET";
|
||||
"formsemestre-bulletins";"/formsemestre/1/bulletins";"ScoView";"GET";
|
||||
"formsemestre-decisions_jury";"/formsemestre/1/decisions_jury";"ScoView";"GET";
|
||||
"formsemestre-etat_evals";"/formsemestre/1/etat_evals";"ScoView";"GET";
|
||||
"formsemestre-etudiants-query";"/formsemestre/1/etudiants/query?etat=D";"ScoView";"GET";
|
||||
"formsemestre-etudiants";"/formsemestre/1/etudiants";"ScoView";"GET";
|
||||
"formsemestre-etudiants";"/formsemestre/1/etudiants/long";"ScoView";"GET";
|
||||
"formsemestre-partition-create";"/formsemestre/1/partition/create";"ScoSuperAdmin";"POST";"{""partition_name"": ""PART""} "
|
||||
"formsemestre-partitions-order";"/formsemestre/1/partitions/order";"ScoSuperAdmin";"POST";"[ 1 ]"
|
||||
"formsemestre-partitions";"/formsemestre/1/partitions";"ScoView";"GET";
|
||||
"formsemestre-programme";"/formsemestre/1/programme";"ScoView";"GET";
|
||||
"formsemestre-resultats";"/formsemestre/1/resultats";"ScoView";"GET";
|
||||
"formsemestre";"/formsemestre/1";"ScoView";"GET";
|
||||
"formsemestres-query";"/formsemestres/query?annee_scolaire=2022&etape_apo=A2";"ScoView";"GET";
|
||||
"formsemestres-query";"/formsemestres/query?nip=11";"ScoView";"GET";
|
||||
"group-delete";"/group/2/delete";"ScoSuperAdmin";"POST";
|
||||
"group-edit";"/group/2/edit";"ScoSuperAdmin";"POST";"{""group_name"": ""NEW_GROUP2""}"
|
||||
"group-etudiants-query";"/group/1/etudiants/query?etat=D";"ScoView";"GET";
|
||||
"group-etudiants";"/group/1/etudiants";"ScoView";"GET";
|
||||
"group-remove_etudiant";"/group/1/remove_etudiant/10";"ScoSuperAdmin";"POST";
|
||||
"group-set_etudiant";"/group/1/set_etudiant/10";"ScoSuperAdmin";"POST";
|
||||
"logo";"/logo/B";"ScoSuperAdmin";"GET";
|
||||
"logos";"/logos";"ScoSuperAdmin";"GET";
|
||||
"moduleimpl-evaluations";"/moduleimpl/1/evaluations";"ScoView";"GET";
|
||||
"moduleimpl";"/moduleimpl/1";"ScoView";"GET";
|
||||
"partition-delete";"/partition/2/delete";"ScoSuperAdmin";"POST";
|
||||
"partition-edit";"/partition/1/edit";"ScoSuperAdmin";"POST";"{""partition_name"":""P2BIS"", ""numero"":3,""bul_show_rank"":true,""show_in_lists"":false, ""groups_editable"":true}"
|
||||
"partition-group-create";"/partition/1/group/create";"ScoSuperAdmin";"POST";"{""group_name"": ""NEW_GROUP""}"
|
||||
"partition-groups-order";"/partition/1/groups/order";"ScoSuperAdmin";"POST";"[ 1 ]"
|
||||
"partition-remove_etudiant";"/partition/2/remove_etudiant/10";"ScoSuperAdmin";"POST";
|
||||
"partition";"/partition/1";"ScoView";"GET";
|
||||
"permissions";"/permissions";"ScoView";"GET";
|
||||
"role-add_permission";"/role/customRole/add_permission/UsersView";"ScoSuperAdmin";"POST";
|
||||
"role-create";"/role/create/customRole";"ScoSuperAdmin";"POST";"{""permissions"": [""ScoView"", ""UsersView""]}"
|
||||
"role-delete";"/role/customRole/delete";"ScoSuperAdmin";"POST";
|
||||
"role-edit";"/role/customRole/edit";"ScoSuperAdmin";"POST";"{ ""name"" : ""LaveurDeVitres"", ""permissions"" : [ ""ScoView"" ] }"
|
||||
"role-remove_permission";"/role/customRole/remove_permission/UsersView";"ScoSuperAdmin";"POST";
|
||||
"role";"/role/Observateur";"ScoView";"GET";
|
||||
"roles";"/roles";"ScoView";"GET";
|
||||
"test-pdf";"/etudiant/etudid/11/formsemestre/1/bulletin";"ScoView";"GET";
|
||||
"test-pdf";"/etudiant/etudid/11/formsemestre/1/bulletin/pdf";"ScoView";"GET";
|
||||
"test-pdf";"/etudiant/etudid/11/formsemestre/1/bulletin/short";"ScoView";"GET";
|
||||
"test-pdf";"/etudiant/etudid/11/formsemestre/1/bulletin/short/pdf";"ScoView";"GET";
|
||||
"test-pdf";"/etudiant/ine/INE11/formsemestre/1/bulletin";"ScoView";"GET";
|
||||
"test-pdf";"/etudiant/ine/INE11/formsemestre/1/bulletin/short";"ScoView";"GET";
|
||||
"test-pdf";"/etudiant/ine/INE11/formsemestre/1/bulletin/short/pdf";"ScoView";"GET";
|
||||
"test-pdf";"/etudiant/nip/11/formsemestre/1/bulletin";"ScoView";"GET";
|
||||
"test-pdf";"/etudiant/nip/11/formsemestre/1/bulletin/pdf";"ScoView";"GET";
|
||||
"test-pdf";"/etudiant/nip/11/formsemestre/1/bulletin/pdf";"ScoView";"GET";
|
||||
"test-pdf";"/etudiant/nip/11/formsemestre/1/bulletin/short";"ScoView";"GET";
|
||||
"test-pdf";"/etudiant/nip/11/formsemestre/1/bulletin/short/pdf";"ScoView";"GET";
|
||||
"user-create";"/user/create";"ScoSuperAdmin";"POST";"{""user_name"": ""alain"", ""dept"": null, ""nom"": ""alain"", ""prenom"": ""bruno"", ""active"": true }"
|
||||
"user-edit";"/user/10/edit";"ScoSuperAdmin";"POST";"{ ""dept"": ""TAPI"", ""nom"": ""alain2"", ""prenom"": ""bruno2"", ""active"": false }"
|
||||
"user-password";"/user/3/password";"ScoSuperAdmin";"POST";"{ ""password"": ""rePlaCemeNT456averylongandcomplicated"" }"
|
||||
"user-password";"/user/3/password";"ScoSuperAdmin";"POST";"{ ""password"": ""too_simple"" }"
|
||||
"user-role-add";"/user/10/role/Observateur/add";"ScoSuperAdmin";"POST";
|
||||
"user-role-remove";"/user/10/role/Observateur/remove";"ScoSuperAdmin";"POST";
|
||||
"user";"/user/1";"ScoView";"GET";
|
||||
"users-query";"/users/query?starts_with=u_";"ScoView";"GET";
|
||||
"formations_ids";"/formations_ids";"ScoView";"GET";
|
||||
"formation_get";"/formation/1";"ScoView";"GET";
|
||||
"formation_export_by_formation_id";"/formation/1/export";"ScoView";"GET";
|
||||
"referentiel_competences";"/formation/1/referentiel_competences";"ScoView";"GET";
|
||||
"formation_module_get";"/formation/module/1";"ScoView";"GET";
|
||||
"formsemestre_get";"/formsemestre/1";"ScoView";"GET";
|
||||
"bulletins";"/formsemestre/1/bulletins";"ScoView";"GET";
|
||||
"formsemestre_programme";"/formsemestre/1/programme";"ScoView";"GET";
|
||||
"formsemestre_etudiants";"/formsemestre/1/etudiants/query";"ScoView";"GET";
|
||||
"formsemestre_etat_evaluations";"/formsemestre/1/etat_evals";"ScoView";"GET";
|
||||
"formsemestre_resultat";"/formsemestre/1/resultats";"ScoView";"GET";
|
||||
"decisions_jury";"/formsemestre/1/decisions_jury";"ScoView";"GET";
|
||||
"justificatif";"/justificatif/1";"ScoView";"GET";
|
||||
"justificatifs";"/justificatifs/1";"ScoView";"GET";
|
||||
"justificatifs";"/justificatifs/1/query?etat=attente";"ScoView";"GET";
|
||||
"justificatifs_dept";"/justificatifs/dept/1";"ScoView";"GET";
|
||||
"justificatifs_formsemestre";"/justificatifs/formsemestre/1";"ScoView";"GET";
|
||||
"justif_edit";"/justificatif/1/edit";"UsersAdmin";"POST";"{""etat"":""valide""}"
|
||||
"justif_edit";"/justificatif/1/edit";"UsersAdmin";"POST";"{""raison"":""MEDIC""}"
|
||||
"justif_delete";"/justificatif/delete";"UsersAdmin";"POST";"[2, 2, 3]"
|
||||
"justif_list";"/justificatif/1/list";"ScoView";"GET";
|
||||
"justif_justifies";"/justificatif/1/justifies";"UsersAdmin";"GET";
|
||||
"logo_list_globals";"/logos";"UsersAdmin";"GET";
|
||||
"logo_get_global";"/logo/B";"UsersAdmin";"GET";
|
||||
"departement_logos";"/departement/TAPI/logos";"UsersAdmin";"GET";
|
||||
"moduleimpl_inscriptions";"/moduleimpl/1/inscriptions";"ScoView";"GET";
|
||||
"moduleimpl_notes";"/moduleimpl/1/notes";"ScoView";"GET";
|
||||
"partition_info";"/partition/1";"ScoView";"GET";
|
||||
"formsemestre_partitions";"/formsemestre/1/partitions";"ScoView";"GET";
|
||||
"group_etudiants";"/group/1/etudiants";"ScoView";"GET";
|
||||
"group_create";"/partition/1/group/create";"ScoView";"POST";"{""group_name"" : ""Nouveau Groupe""}"
|
||||
"group_edit";"/group/1/edit";"ScoView";"POST";"{""group_name"":""A1""}"
|
||||
"group_set_edt_id";"/group/1/set_edt_id/EDT_GR1";"ScoView";"POST";
|
||||
"partition_edit";"/partition/1/edit";"ScoView";"POST";"{""bul_show_rank"":1}"
|
||||
"user_info";"/user/2";"UsersAdmin";"GET";
|
||||
"user_password";"/user/3/password";"UsersAdmin";"POST";"{""password"" : ""rePlaCemeNT456averylongandcomplicated""}"
|
||||
"permissions_list";"/permissions";"UsersAdmin";"GET";
|
||||
"role_get";"/role/Ens";"UsersAdmin";"GET";
|
||||
"roles_list";"/roles";"UsersAdmin";"GET";
|
||||
"role_create";"/role/create/customRole";"UsersAdmin";"POST";"{""permissions"": [""ScoView"", ""UsersView""]}"
|
||||
"role_delete";"/role/customRole/delete";"UsersAdmin";"POST";
|
Can't render this file because it contains an unexpected character in line 41 and column 2.
|
@ -10,4 +10,4 @@ from tools.migrate_scodoc7_archives import migrate_scodoc7_dept_archives
|
||||
from tools.migrate_scodoc7_logos import migrate_scodoc7_dept_logos
|
||||
from tools.migrate_abs_to_assiduites import migrate_abs_to_assiduites
|
||||
from tools.downgrade_assiduites import downgrade_module
|
||||
from tools.create_api_map import gen_api_map
|
||||
from tools.create_api_map import gen_api_map, gen_api_doc
|
||||
|
@ -4,8 +4,14 @@ Script permettant de générer une carte SVG de l'API de ScoDoc
|
||||
Écrit par Matthias HARTMANN
|
||||
"""
|
||||
|
||||
import xml.etree.ElementTree as ET
|
||||
import re
|
||||
import sys
|
||||
import unicodedata
|
||||
import xml.etree.ElementTree as ET
|
||||
|
||||
from flask import render_template
|
||||
|
||||
from app.auth.models import Permission
|
||||
|
||||
|
||||
class COLORS:
|
||||
@ -132,7 +138,7 @@ class Token:
|
||||
element, x_offset, y_offset
|
||||
)
|
||||
# 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"):
|
||||
href += "-query"
|
||||
question_mark_group = _create_question_mark_group(current_end_coords, href)
|
||||
@ -266,6 +272,13 @@ class Token:
|
||||
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)"):
|
||||
"""
|
||||
Fonction générale pour créer un élément SVG simple
|
||||
@ -437,16 +450,15 @@ def _create_question_mark_group(coords, href):
|
||||
return group
|
||||
|
||||
|
||||
def gen_api_map(app, endpoint_start="api"):
|
||||
"""
|
||||
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
|
||||
puis génère un fichier SVG à partir de cet arbre
|
||||
def analyze_api_routes(app, endpoint_start: str) -> tuple:
|
||||
"""Parcours de toutes les routes de l'application
|
||||
analyse docstrings
|
||||
"""
|
||||
# Création du token racine
|
||||
api_map = Token("")
|
||||
|
||||
# Parcours de toutes les routes de l'application
|
||||
doctable_lines: dict[str, dict] = {}
|
||||
|
||||
for rule in app.url_map.iter_rules():
|
||||
# On ne garde que les routes de l'API / APIWEB
|
||||
if not rule.endpoint.lower().startswith(endpoint_start.lower()):
|
||||
@ -461,8 +473,8 @@ def gen_api_map(app, endpoint_start="api"):
|
||||
|
||||
# Récupération de la fonction associée à la route
|
||||
func = app.view_functions[rule.endpoint]
|
||||
func_name = parse_doc_name(func.__doc__ or "") or func.__name__
|
||||
|
||||
doc_dict = _parse_doc_string(func.__doc__ or "")
|
||||
func_name = doc_dict.get("DOC_ANCHOR", [None])[0] or func.__name__
|
||||
# Pour chaque segment de la route
|
||||
for i, segment in enumerate(segments):
|
||||
# On cherche si le segment est déjà un enfant du token courant
|
||||
@ -500,8 +512,55 @@ def gen_api_map(app, endpoint_start="api"):
|
||||
child.method = method
|
||||
current_token.add_child(child)
|
||||
|
||||
href = func_name
|
||||
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(
|
||||
sorted(Permission.permissions_names(func.scodoc_permission))
|
||||
)
|
||||
except AttributeError:
|
||||
permissions = "Aucune permission requise"
|
||||
|
||||
if func_name not in doctable_lines:
|
||||
doctable_lines[func_name] = {
|
||||
"method": method,
|
||||
"nom": func_name,
|
||||
"href": href,
|
||||
"query": doc_dict.get("QUERY", "") != "",
|
||||
"permission": permissions,
|
||||
"description": doc_dict.get("", ""),
|
||||
"params": doc_dict.get("PARAMS", ""),
|
||||
"category": doc_dict.get("CATEGORY", [False])[0] or category,
|
||||
"samples": doc_dict.get("SAMPLES"),
|
||||
}
|
||||
|
||||
# On met à jour le token courant pour le prochain segment
|
||||
current_token = child
|
||||
if func_name in doctable_lines: # endpoint déjà ajouté, ajoute au besoin route
|
||||
doctable_lines[func_name]["routes"] = doctable_lines[func_name].get(
|
||||
"routes", []
|
||||
) + [rule.rule]
|
||||
return api_map, doctable_lines
|
||||
|
||||
|
||||
# point d'entrée de la commande `flask gen-api-map`
|
||||
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
|
||||
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
|
||||
"""
|
||||
|
||||
# On génère le SVG à partir de l'arbre de Token
|
||||
generate_svg(api_map.to_svg_group(), "/tmp/api_map.svg")
|
||||
@ -510,6 +569,11 @@ def gen_api_map(app, endpoint_start="api"):
|
||||
+ "Vous pouvez la consulter à l'adresse suivante : /tmp/api_map.svg"
|
||||
)
|
||||
|
||||
# On génère le tableau à partir de doctable_lines
|
||||
table = _gen_table(sorted(doctable_lines.values(), key=lambda x: x["nom"]))
|
||||
_write_gen_table(table)
|
||||
return table
|
||||
|
||||
|
||||
def _get_bbox(element, x_offset=0, y_offset=0):
|
||||
"""
|
||||
@ -614,9 +678,47 @@ def generate_svg(element, fname):
|
||||
tree.write(fname, encoding="utf-8", xml_declaration=True)
|
||||
|
||||
|
||||
def _get_doc_lines(keyword, doc) -> list[str]:
|
||||
def _parse_doc_string(doc_string: str) -> dict[str, list[str]]:
|
||||
"""Parse doc string and extract a dict:
|
||||
{
|
||||
"" : description_lines,
|
||||
"keyword" : lines
|
||||
}
|
||||
|
||||
In the docstring, each keyword is associated to a section like
|
||||
|
||||
KEYWORD
|
||||
-------
|
||||
...
|
||||
(blank line)
|
||||
|
||||
All non blank lines not associated to a keyword go to description.
|
||||
"""
|
||||
doc_dict = {}
|
||||
matches = re.finditer(
|
||||
r"^\s*(?P<kw>[A-Z_\-]+)$\n^\s*-+\n(?P<txt>(^(?!\s*$).+$\n?)+)",
|
||||
doc_string,
|
||||
re.MULTILINE,
|
||||
)
|
||||
description = ""
|
||||
i = 0
|
||||
for match in matches:
|
||||
start, end = match.span()
|
||||
description += doc_string[i:start]
|
||||
doc_dict[match.group("kw")] = [
|
||||
x.strip() for x in match.group("txt").split("\n") if x.strip()
|
||||
]
|
||||
i = end
|
||||
|
||||
description += doc_string[i:]
|
||||
doc_dict[""] = description.split("\n")
|
||||
return doc_dict
|
||||
|
||||
|
||||
def _get_doc_lines(keyword, doc_string: str) -> list[str]:
|
||||
"""
|
||||
Renvoie les lignes de la doc qui suivent le mot clé keyword
|
||||
Attention : s'arrête à la première ligne vide
|
||||
|
||||
La doc doit contenir des lignes de la forme:
|
||||
|
||||
@ -626,7 +728,7 @@ def _get_doc_lines(keyword, doc) -> list[str]:
|
||||
|
||||
"""
|
||||
# Récupérer les lignes de la doc
|
||||
lines = [line.strip() for line in doc.split("\n")]
|
||||
lines = [line.strip() for line in doc_string.split("\n")]
|
||||
# On cherche la ligne "KEYWORD" et on vérifie que la ligne suivante est "-----"
|
||||
# Si ce n'est pas le cas, on renvoie un dictionnaire vide
|
||||
try:
|
||||
@ -638,27 +740,18 @@ def _get_doc_lines(keyword, doc) -> list[str]:
|
||||
return []
|
||||
# On récupère les lignes de la doc qui correspondent au keyword (enfin on espère)
|
||||
kw_lines = lines[kw_index + 2 :]
|
||||
|
||||
# On s'arrête à la première ligne vide
|
||||
first_empty_line: int
|
||||
try:
|
||||
first_empty_line: int = kw_lines.index("")
|
||||
except ValueError:
|
||||
first_empty_line = len(kw_lines)
|
||||
kw_lines = kw_lines[:first_empty_line]
|
||||
return kw_lines
|
||||
|
||||
|
||||
def parse_doc_name(doc):
|
||||
"""
|
||||
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)
|
||||
return name_lines[0] if name_lines else None
|
||||
|
||||
|
||||
def parse_query_doc(doc):
|
||||
def parse_query_doc(doc_string: str) -> dict[str, str]:
|
||||
"""
|
||||
renvoie un dictionnaire {param: <type:nom_param>} (ex: {assiduite_id : <int:assiduite_id>})
|
||||
|
||||
@ -673,7 +766,7 @@ def parse_query_doc(doc):
|
||||
Dès qu'une ligne ne respecte pas ce format (voir regex dans la fonction), on arrête de parser
|
||||
Attention, la ligne ----- doit être collée contre QUERY et contre le premier paramètre
|
||||
"""
|
||||
query_lines: list[str] = _get_doc_lines("QUERY", doc)
|
||||
query_lines: list[str] = _get_doc_lines("QUERY", doc_string)
|
||||
|
||||
query = {}
|
||||
regex = re.compile(r"^(\w+):(<.+>)$")
|
||||
@ -691,33 +784,190 @@ def parse_query_doc(doc):
|
||||
return query
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# Exemple d'utilisation de la classe Token
|
||||
# Exemple simple de création d'un arbre de Token
|
||||
def _gen_table_line(doctable: dict = None):
|
||||
"""
|
||||
Génère une ligne de tableau markdown
|
||||
|
||||
root = Token("api")
|
||||
child1 = Token("assiduites", leaf=True)
|
||||
child1.func_name = "assiduites_get"
|
||||
child2 = Token("count")
|
||||
child22 = Token("all")
|
||||
child23 = Token(
|
||||
"query",
|
||||
query={
|
||||
"etat": "<string:etat>",
|
||||
"moduleimpl_id": "<int:moduleimpl_id>",
|
||||
"count": "<int:count>",
|
||||
"formsemestre_id": "<int:formsemestre_id>",
|
||||
},
|
||||
| nom de la route| methode HTTP| Permission |
|
||||
"""
|
||||
|
||||
nom, method, permission = (
|
||||
doctable.get("nom", ""),
|
||||
doctable.get("method", ""),
|
||||
doctable.get("permission", ""),
|
||||
)
|
||||
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)
|
||||
if doctable is None:
|
||||
doctable = {}
|
||||
|
||||
group_element = root.to_svg_group()
|
||||
lien: str = doctable.get("href", nom)
|
||||
|
||||
generate_svg(group_element, "/tmp/api_map.svg")
|
||||
doctable["query"]: bool
|
||||
if doctable.get("query") and not lien.endswith("-query"):
|
||||
lien += "-query"
|
||||
|
||||
nav: str = f"[{nom}]({'#'+lien})"
|
||||
|
||||
table: str = "|"
|
||||
for string in [nav, method, doctable.get("permissions") or permission]:
|
||||
table += f" {string} |"
|
||||
|
||||
return table
|
||||
|
||||
|
||||
def _gen_table_head() -> str:
|
||||
"""
|
||||
Génère la première ligne du tableau markdown
|
||||
"""
|
||||
|
||||
headers: str = "| Route | Méthode | Permission |"
|
||||
line: str = "|---|---|---|"
|
||||
|
||||
return f"{headers}\n{line}\n"
|
||||
|
||||
|
||||
def _gen_table(lines: list[dict]) -> str:
|
||||
"""
|
||||
Génère un tableau markdown à partir d'une liste de lignes
|
||||
|
||||
lines : liste de dictionnaire au format doc_lines.
|
||||
"""
|
||||
table = _gen_table_head()
|
||||
table += "\n".join([_gen_table_line(line) for line in lines])
|
||||
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:
|
||||
fragments = sample.split(";", maxsplit=1)
|
||||
if len(fragments) == 2:
|
||||
url, content = fragments
|
||||
elif len(fragments) == 1:
|
||||
url, content = fragments[0], ""
|
||||
else:
|
||||
raise ValueError(f"Error: sample invalide: {sample}")
|
||||
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"):
|
||||
"""Ecriture du fichier md avec la table"""
|
||||
with open(filename, "w", encoding="UTF-8") as f:
|
||||
f.write(table)
|
||||
print(
|
||||
f"Le tableau a été généré avec succès. Vous pouvez le consulter à l'adresse suivante : {filename}"
|
||||
)
|
||||
|
||||
|
||||
def doc_route(doctable: dict) -> str:
|
||||
"""Generate markdown doc for a route"""
|
||||
jinja_obj: dict = {}
|
||||
jinja_obj.update(doctable)
|
||||
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"):
|
||||
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}")
|
||||
if doctable.get("description"):
|
||||
descr = "\n".join(s for s in doctable["description"])
|
||||
jinja_obj["description"] = descr.strip()
|
||||
|
||||
return render_template("doc/apidoc.j2", doc=jinja_obj)
|
||||
|
||||
|
||||
def gen_api_doc(app, endpoint_start="api."):
|
||||
"commande gen-api-doc"
|
||||
api_map, doctable_lines = analyze_api_routes(app, endpoint_start)
|
||||
|
||||
mddoc: str = ""
|
||||
|
||||
categories: dict = {}
|
||||
for value in doctable_lines.values():
|
||||
category = value["category"].capitalize()
|
||||
if category not in categories:
|
||||
categories[category] = []
|
||||
categories[category].append(value)
|
||||
|
||||
# sort categories by name
|
||||
categories: dict = dict(
|
||||
sorted(categories.items(), key=lambda x: strip_accents(x[0]))
|
||||
)
|
||||
|
||||
category: str
|
||||
routes: list[dict]
|
||||
for category, routes in categories.items():
|
||||
# sort routes by name
|
||||
routes.sort(key=lambda x: strip_accents(x["nom"]))
|
||||
|
||||
mddoc += f"### API {category.capitalize()}\n\n"
|
||||
for route in routes:
|
||||
mddoc += doc_route(route)
|
||||
mddoc += "\n\n"
|
||||
|
||||
table_api = gen_api_map(api_map, doctable_lines)
|
||||
mdpage = render_template("doc/ScoDoc9API.j2", doc_api=mddoc, table_api=table_api)
|
||||
_gen_csv(list(doctable_lines.values()))
|
||||
|
||||
fname = "/tmp/ScoDoc9API.md"
|
||||
with open(fname, "w", encoding="utf-8") as f:
|
||||
f.write(mdpage)
|
||||
print(
|
||||
"La documentation API a été générée avec succès. "
|
||||
f"Vous pouvez la consulter à l'adresse suivante : {fname}"
|
||||
)
|
||||
|
@ -211,10 +211,17 @@ def create_formsemestre(
|
||||
)
|
||||
db.session.add(modimpl)
|
||||
db.session.commit()
|
||||
# Partition par défaut (requise):
|
||||
partition_id = sco_groups.partition_create(
|
||||
formsemestre.id, default=True, redirect=False
|
||||
)
|
||||
group = sco_groups.create_group(partition_id, default=True)
|
||||
sco_groups.create_group(partition_id, default=True)
|
||||
# Ajoute partition normale, TD avec groupes A et B:
|
||||
partition_id = sco_groups.partition_create(
|
||||
formsemestre.id, partition_name="TD", redirect=False
|
||||
)
|
||||
sco_groups.create_group(partition_id, group_name="A")
|
||||
sco_groups.create_group(partition_id, group_name="B")
|
||||
return formsemestre
|
||||
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user