API: revision pour multi-départements

This commit is contained in:
Emmanuel Viennet 2022-05-11 00:59:51 +02:00
parent 1209cf4ef3
commit f9ec454da5
7 changed files with 396 additions and 351 deletions

View File

@ -5,26 +5,21 @@ from flask import jsonify
from app.api import bp
from app.api.errors import error_response
from app.api.auth import token_auth, token_permission_required
from app.api.tools import get_etud_from_etudid_or_nip_or_ine
from app.scodoc import notesdb as ndb
from app.models import Identite
from app.scodoc import notesdb as ndb
from app.scodoc import sco_abs
from app.scodoc.sco_groups import get_group_members
from app.scodoc.sco_permissions import Permission
@bp.route("/absences/etudid/<int:etudid>", methods=["GET"])
@bp.route("/absences/nip/<string:nip>", methods=["GET"])
@bp.route("/absences/ine/<string:ine>", methods=["GET"])
@token_auth.login_required
@token_permission_required(Permission.APIView)
def absences(etudid: int = None, nip: int = None, ine: int = None):
def absences(etudid: int = None):
"""
Retourne la liste des absences d'un étudiant donné
etudid : l'etudid d'un étudiant
nip: le code nip d'un étudiant
ine : le code ine d'un étudiant
Exemple de résultat:
[
@ -48,30 +43,24 @@ def absences(etudid: int = None, nip: int = None, ine: int = None):
}
]
"""
if etudid is None:
# Récupération de l'étudiant
etud = get_etud_from_etudid_or_nip_or_ine(etudid, nip, ine)
etud = Identite.query.get(etudid)
if etud is None:
return error_response(
404,
message="id de l'étudiant (etudid, nip, ine) inconnu",
)
etudid = etud.etudid
# Récupération des absences de l'étudiant
# Absences de l'étudiant
ndb.open_db_connection()
absences = sco_abs.list_abs_date(etudid)
absences = sco_abs.list_abs_date(etud.id)
for absence in absences:
absence["jour"] = absence["jour"].isoformat()
return jsonify(absences)
@bp.route("/absences/etudid/<int:etudid>/just", methods=["GET"])
@bp.route("/absences/nip/<int:nip>/just", methods=["GET"])
@bp.route("/absences/ine/<int:ine>/just", methods=["GET"])
@token_auth.login_required
@token_permission_required(Permission.APIView)
def absences_just(etudid: int = None, nip: int = None, ine: int = None):
def absences_just(etudid: int = None):
"""
Retourne la liste des absences justifiées d'un étudiant donné
@ -101,18 +90,16 @@ def absences_just(etudid: int = None, nip: int = None, ine: int = None):
}
]
"""
if etudid is None:
etud = get_etud_from_etudid_or_nip_or_ine(etudid, nip, ine)
etud = Identite.query.get(etudid)
if etud is None:
return error_response(
404,
message="id de l'étudiant (etudid, nip, ine) inconnu",
)
etudid = etud.etudid
# Récupération des absences justifiées de l'étudiant
# Absences justifiées de l'étudiant
abs_just = [
absence for absence in sco_abs.list_abs_date(etudid) if absence["estjust"]
absence for absence in sco_abs.list_abs_date(etud.id) if absence["estjust"]
]
for absence in abs_just:
absence["jour"] = absence["jour"].isoformat()

View File

@ -1,14 +1,20 @@
#################################################### Etudiants ########################################################
##############################################################################
# ScoDoc
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
# See LICENSE
##############################################################################
"""
API : accès aux étudiants
"""
from flask import jsonify
import app
from app import models
from app.api import bp
from app.api.errors import error_response
from app.api.auth import token_auth, token_permission_required
from app.api.tools import get_etud_from_etudid_or_nip_or_ine
from app.models import FormSemestreInscription, FormSemestre, Identite
from app.models import Departement, FormSemestreInscription, FormSemestre, Identite
from app.scodoc import sco_bulletins
from app.scodoc import sco_groups
from app.scodoc.sco_permissions import Permission
@ -59,13 +65,16 @@ def etudiants_courant(long=False):
@bp.route("/etudiant/ine/<string:ine>", methods=["GET"])
@token_auth.login_required
@token_permission_required(Permission.APIView)
def etudiant(etudid: int = None, nip: int = None, ine: int = None):
def etudiant(etudid: int = None, nip: str = None, ine: str = None):
"""
Retourne les informations de l'étudiant correspondant à l'id passé en paramètres.
Retourne les informations de l'étudiant correspondant, ou 404 si non trouvé.
etudid : l'etudid d'un étudiant
nip : le code nip d'un étudiant
ine : le code ine d'un étudiant
etudid : l'etudid de l'étudiant
nip : le code nip de l'étudiant
ine : le code ine de l'étudiant
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.
Exemple de résultat :
{
@ -95,17 +104,63 @@ def etudiant(etudid: int = None, nip: int = None, ine: int = None):
"description": ""
}
"""
# Récupération de l'étudiant
etud = get_etud_from_etudid_or_nip_or_ine(etudid, nip, ine)
if etudid is not None:
etud = Identite.query.get(etudid)
else:
if nip is not None:
query = Identite.query.filter_by(code_nip=nip)
elif ine is not None:
query = Identite.query.filter_by(code_ine=ine)
else:
return error_response(
404,
message="parametre manquant",
)
if query.count() > 1: # cas rare d'un étudiant présent dans plusieurs depts
etuds = []
for e in query:
admission = e.admission.first()
etuds.append((((admission.annee or 0) if admission else 0), e))
etuds.sort()
etud = etuds[-1][1]
else:
etud = query.first()
if etud is None:
return error_response(
404,
message="id de l'étudiant (etudid, nip, ine) inconnu",
message="étudiant inconnu",
)
# Mise en forme des données
data = etud.to_dict_bul(include_urls=False)
return jsonify(data)
return jsonify(etud.to_dict_bul(include_urls=False))
@bp.route("/etudiants/etudid/<int:etudid>", methods=["GET"])
@bp.route("/etudiants/nip/<string:nip>", methods=["GET"])
@bp.route("/etudiants/ine/<string:ine>", methods=["GET"])
@token_auth.login_required
@token_permission_required(Permission.APIView)
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.
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.).
"""
if etudid is not None:
query = Identite.query.filter_by(id=etudid)
elif nip is not None:
query = Identite.query.filter_by(code_nip=nip)
elif ine is not None:
query = Identite.query.filter_by(code_ine=ine)
else:
return error_response(
404,
message="parametre manquant",
)
return jsonify([etud.to_dict_bul(include_urls=False) for etud in query])
@bp.route("/etudiant/etudid/<int:etudid>/formsemestres")
@ -115,11 +170,8 @@ def etudiant(etudid: int = None, nip: int = None, ine: int = None):
@token_permission_required(Permission.APIView)
def etudiant_formsemestres(etudid: int = None, nip: int = None, ine: int = None):
"""
Retourne la liste des semestres qu'un étudiant a suivis, triés par ordre chronologique.
etudid : l'etudid d'un étudiant
nip : le code nip d'un étudiant
ine : le code ine d'un étudiant
Liste des semestres qu'un étudiant a suivi, triés par ordre chronologique.
Accès par etudid, nip ou ine
Exemple de résultat :
[
@ -153,18 +205,30 @@ def etudiant_formsemestres(etudid: int = None, nip: int = None, ine: int = None)
...
]
"""
# Récupération de l'étudiant
etud = get_etud_from_etudid_or_nip_or_ine(etudid, nip, ine)
if etud is None:
if etudid is not None:
query = FormSemestre.query.filter(
FormSemestreInscription.etudid == etudid,
FormSemestreInscription.formsemestre_id == FormSemestre.id,
)
elif nip is not None:
query = FormSemestre.query.filter(
Identite.code_nip == nip,
FormSemestreInscription.etudid == Identite.id,
FormSemestreInscription.formsemestre_id == FormSemestre.id,
)
elif ine is not None:
query = FormSemestre.query.filter(
Identite.code_ine == ine,
FormSemestreInscription.etudid == Identite.id,
FormSemestreInscription.formsemestre_id == FormSemestre.id,
)
else:
return error_response(
404,
message="id de l'étudiant (etudid, nip, ine) inconnu",
message="parametre manquant",
)
formsemestres = models.FormSemestre.query.filter(
models.FormSemestreInscription.etudid == etud.id,
models.FormSemestreInscription.formsemestre_id == models.FormSemestre.id,
).order_by(models.FormSemestre.date_debut)
formsemestres = query.order_by(FormSemestre.date_debut)
return jsonify([formsemestre.to_dict() for formsemestre in formsemestres])
@ -204,8 +268,8 @@ def etudiant_formsemestres(etudid: int = None, nip: int = None, ine: int = None)
def etudiant_bulletin_semestre(
formsemestre_id,
etudid: int = None,
nip: int = None,
ine: int = None,
nip: str = None,
ine: str = None,
version="long",
):
"""
@ -247,13 +311,13 @@ def etudiant_bulletin_semestre(
"villedomicile": "",
"telephone": "",
"fax": "",
"description": ""
"description": "",
},
"formation": {
"id": 1,
"acronyme": "BUT R&amp;T",
"titre_officiel": "Bachelor technologique r\u00e9seaux et t\u00e9l\u00e9communications",
"titre": "BUT R&amp;T"
"titre": "BUT R&amp;T",
},
"formsemestre_id": 1,
"etat_inscription": "I",
@ -276,7 +340,7 @@ def etudiant_bulletin_semestre(
"show_temporary": true,
"temporary_txt": "Provisoire",
"show_uevalid": true,
"show_date_inscr": true
"show_date_inscr": true,
},
"ressources": {
"R101": {
@ -300,11 +364,11 @@ def etudiant_bulletin_semestre(
"value": "12.00",
"min": "00.00",
"max": "18.00",
"moy": "10.88"
"moy": "10.88",
},
"url": "/ScoDoc/TAPI/Scolarite/Notes/evaluation_listenotes?evaluation_id=1"
"url": "/ScoDoc/TAPI/Scolarite/Notes/evaluation_listenotes?evaluation_id=1",
}
]
],
},
},
"saes": {
@ -314,7 +378,7 @@ def etudiant_bulletin_semestre(
"code_apogee": null,
"url": "/ScoDoc/TAPI/Scolarite/Notes/moduleimpl_status?moduleimpl_id=2",
"moyenne": {},
"evaluations": []
"evaluations": [],
},
},
"ues": {
@ -331,29 +395,18 @@ def etudiant_bulletin_semestre(
"max": "16.50",
"moy": "11.31",
"rang": "12",
"total": 16
"total": 16,
},
"bonus": "00.00",
"malus": "00.00",
"capitalise": null,
"ressources": {
"R101": {
"id": 1,
"coef": 12.0,
"moyenne": "12.00"
},
"R101": {"id": 1, "coef": 12.0, "moyenne": "12.00"},
},
"saes": {
"SAE11": {
"id": 2,
"coef": 16.0,
"moyenne": "~"
"SAE11": {"id": 2, "coef": 16.0, "moyenne": "~"},
},
},
"ECTS": {
"acquis": 0.0,
"total": 12.0
}
"ECTS": {"acquis": 0.0, "total": 12.0},
},
"semestre": {
"etapes": [],
@ -363,41 +416,37 @@ def etudiant_bulletin_semestre(
"numero": 1,
"inscription": "",
"groupes": [],
"absences": {
"injustifie": 1,
"total": 2
"absences": {"injustifie": 1, "total": 2},
"ECTS": {"acquis": 0, "total": 30.0},
"notes": {"value": "10.60", "min": "02.40", "moy": "11.05", "max": "17.40"},
"rang": {"value": "10", "total": 16},
},
"ECTS": {
"acquis": 0,
"total": 30.0
},
"notes": {
"value": "10.60",
"min": "02.40",
"moy": "11.05",
"max": "17.40"
},
"rang": {
"value": "10",
"total": 16
}
}
}
"""
formsemestre = models.FormSemestre.query.filter_by(
id=formsemestre_id
).first_or_404()
formsemestre = FormSemestre.query.filter_by(id=formsemestre_id).first_or_404()
dept = Departement.query.filter_by(id=formsemestre.dept_id).first_or_404()
dept = models.Departement.query.filter_by(id=formsemestre.dept_id).first_or_404()
if etudid is not None:
query = Identite.query.filter_by(id=etudid)
elif nip is not None:
query = Identite.query.filter_by(code_nip=nip, dept_id=dept.id)
elif ine is not None:
query = Identite.query.filter_by(code_ine=ine, dept_id=dept.id)
else:
return error_response(
404,
message="parametre manquant",
)
app.set_sco_dept(dept.acronym)
etud = get_etud_from_etudid_or_nip_or_ine(etudid, nip, ine)
etud = query.first()
if etud is None:
return error_response(
404,
message="id de l'étudiant (etudid, nip, ine) inconnu",
)
app.set_sco_dept(dept.acronym)
return sco_bulletins.get_formsemestre_bulletin_etud_json(
formsemestre, etud, version
)
@ -454,19 +503,32 @@ def etudiant_groups(
}
]
"""
if etudid is None:
etud = get_etud_from_etudid_or_nip_or_ine(etudid, nip, ine)
formsemestre = FormSemestre.query.filter_by(id=formsemestre_id).first()
if formsemestre is None:
return error_response(
404,
message="formsemestre inconnu",
)
dept = Departement.query.get(formsemestre.dept_id)
if etudid is not None:
query = Identite.query.filter_by(id=etudid)
elif nip is not None:
query = Identite.query.filter_by(code_nip=nip, dept_id=dept.id)
elif ine is not None:
query = Identite.query.filter_by(code_ine=ine, dept_id=dept.id)
else:
return error_response(
404,
message="parametre manquant",
)
etud = query.first()
if etud is None:
return error_response(
404,
message="id de l'étudiant (etudid, nip, ine) inconnu",
message="etudiant inconnu",
)
etudid = etud.etudid
# Récupération du formsemestre
sem = models.FormSemestre.query.filter_by(id=formsemestre_id).first_or_404()
dept = models.Departement.query.get(sem.dept_id)
app.set_sco_dept(dept.acronym)
data = sco_groups.get_etud_groups(etudid, sem.id)
data = sco_groups.get_etud_groups(etud.id, formsemestre.id)
return jsonify(data)

View File

@ -178,6 +178,7 @@ class Identite(db.Model):
"date_naissance": self.date_naissance.strftime("%d/%m/%Y")
if self.date_naissance
else "",
"dept_id": self.dept_id,
"email": self.get_first_email() or "",
"emailperso": self.get_first_email("emailperso"),
"etudid": self.id,

View File

@ -26,13 +26,17 @@ import urllib3
from pprint import pprint as pp
# --- Lecture configuration (variables d'env ou .env)
try:
BASEDIR = os.path.abspath(os.path.dirname(__file__))
except NameError:
BASEDIR = "."
load_dotenv(os.path.join(BASEDIR, ".env"))
CHK_CERT = bool(int(os.environ.get("CHECK_CERTIFICATE", False)))
SCODOC_URL = os.environ["SCODOC_URL"]
API_URL = SCODOC_URL + "/ScoDoc/api"
SCODOC_USER = os.environ["SCODOC_USER"]
SCODOC_PASSWORD = os.environ["SCODOC_PASSWD"]
SCODOC_PASSWORD = os.environ["SCODOC_PASSWORD"]
print(f"SCODOC_URL={SCODOC_URL}")
print(f"API URL={API_URL}")
@ -90,6 +94,23 @@ formsemestre_id = 1028 # A adapter
etudid = 14721
bul_dut = GET(f"/etudiant/etudid/{etudid}/formsemestre/{formsemestre_id}/bulletin")
# Infos sur un étudiant
etudid = 3561
code_nip = "11303314"
etud = GET(f"/etudiant/etudid/{etudid}")
print(etud)
etud = GET(f"/etudiant/nip/{code_nip}")
print(etud)
sems = GET(f"/etudiant/etudid/{etudid}/formsemestres")
print("\n".join([s["titre_num"] for s in sems]))
sems = GET(f"/etudiant/nip/{code_nip}/formsemestres")
print("\n".join([s["titre_num"] for s in sems]))
# # --- Recupere la liste de tous les semestres:
# sems = GET(s, "Notes/formsemestre_list?format=json", "Aucun semestre !")

View File

@ -22,8 +22,7 @@ from tests.api.setup_test_api import API_URL, CHECK_CERTIFICATE, api_headers
# Etudiant pour les tests
ETUDID = 1
INE = "1"
NIP = "1"
# absences
def test_absences(api_headers):
@ -37,20 +36,6 @@ def test_absences(api_headers):
)
assert r.status_code == 200
r = requests.get(
f"{API_URL}/absences/nip/{NIP}",
headers=api_headers,
verify=CHECK_CERTIFICATE,
)
assert r.status_code == 200
r = requests.get(
f"{API_URL}/absences/ine/{INE}",
headers=api_headers,
verify=CHECK_CERTIFICATE,
)
assert r.status_code == 200
# absences_justify
def test_absences_justify(api_headers):
@ -65,22 +50,6 @@ def test_absences_justify(api_headers):
assert r.status_code == 200
# TODO vérifier résultat
r = requests.get(
API_URL + f"/absences/nip/{NIP}/just",
headers=api_headers,
verify=CHECK_CERTIFICATE,
)
assert r.status_code == 200
# TODO vérifier résultat
r = requests.get(
API_URL + f"/absences/ine/{INE}/just",
headers=api_headers,
verify=CHECK_CERTIFICATE,
)
assert r.status_code == 200
# TODO vérifier résultat
# XXX TODO
# def test_abs_groupe_etat(api_headers):

View File

@ -93,7 +93,7 @@ def test_etudiant(api_headers):
)
assert r.status_code == 200
etud = r.json()
assert len(etud) == 24
assert len(etud) == 25
fields_ok = verify_fields(etud, ETUD_FIELDS)
assert fields_ok is True

View File

@ -24,6 +24,11 @@ from tests.api.setup_test_api import API_URL, CHECK_CERTIFICATE, api_headers
from tests.api.tools_test_api import MODIMPL_FIELDS, verify_fields
from tests.api.tools_test_api import FSEM_FIELDS, UE_FIELDS, MODULE_FIELDS
# Etudiant pour les tests
ETUDID = 1
NIP = "1"
INE = "INE1"
def test_formsemestre(api_headers):
"""
@ -53,7 +58,7 @@ def test_etudiant_bulletin(api_headers):
bull_a = r.json()
r = requests.get(
f"{API_URL}/etudiant/nip/1/formsemestre/{formsemestre_id}/bulletin",
f"{API_URL}/etudiant/nip/{NIP}/formsemestre/{formsemestre_id}/bulletin",
headers=api_headers,
verify=CHECK_CERTIFICATE,
)
@ -61,7 +66,7 @@ def test_etudiant_bulletin(api_headers):
bull_b = r.json()
r = requests.get(
f"{API_URL}/etudiant/ine/1/formsemestre/{formsemestre_id}/bulletin",
f"{API_URL}/etudiant/ine/{INE}/formsemestre/{formsemestre_id}/bulletin",
headers=api_headers,
verify=CHECK_CERTIFICATE,
)