forked from ScoDoc/ScoDoc
432 lines
15 KiB
Python
Executable File
432 lines
15 KiB
Python
Executable File
##############################################################################
|
|
# ScoDoc
|
|
# Copyright (c) 1999 - 2023 Emmanuel Viennet. All rights reserved.
|
|
# See LICENSE
|
|
##############################################################################
|
|
|
|
"""
|
|
API : accès aux étudiants
|
|
"""
|
|
from datetime import datetime
|
|
from operator import attrgetter
|
|
|
|
from flask import g, request
|
|
from flask_json import as_json
|
|
from flask_login import current_user
|
|
from flask_login import login_required
|
|
from sqlalchemy import desc, func, or_
|
|
from sqlalchemy.dialects.postgresql import VARCHAR
|
|
|
|
import app
|
|
from app.api import api_bp as bp, api_web_bp
|
|
from app.api import tools
|
|
from app.decorators import scodoc, permission_required
|
|
from app.models import (
|
|
Admission,
|
|
Departement,
|
|
FormSemestreInscription,
|
|
FormSemestre,
|
|
Identite,
|
|
)
|
|
from app.scodoc import sco_bulletins
|
|
from app.scodoc import sco_groups
|
|
from app.scodoc.sco_bulletins import do_formsemestre_bulletinetud
|
|
from app.scodoc.sco_permissions import Permission
|
|
from app.scodoc.sco_utils import json_error, suppress_accents
|
|
|
|
import app.scodoc.sco_photos as sco_photos
|
|
|
|
# Un exemple:
|
|
# @bp.route("/api_function/<int:arg>")
|
|
# @api_web_bp.route("/api_function/<int:arg>")
|
|
# @login_required
|
|
# @scodoc
|
|
# @permission_required(Permission.ScoView)
|
|
# @as_json
|
|
# def api_function(arg: int):
|
|
# """Une fonction quelconque de l'API"""
|
|
# return {"current_user": current_user.to_dict(), "arg": arg, "dept": g.scodoc_dept}
|
|
#
|
|
|
|
|
|
@bp.route("/etudiants/courants", defaults={"long": False})
|
|
@bp.route("/etudiants/courants/long", defaults={"long": True})
|
|
@api_web_bp.route("/etudiants/courants", defaults={"long": False})
|
|
@api_web_bp.route("/etudiants/courants/long", defaults={"long": True})
|
|
@login_required
|
|
@scodoc
|
|
@permission_required(Permission.ScoView)
|
|
@as_json
|
|
def etudiants_courants(long=False):
|
|
"""
|
|
La liste des étudiants des semestres "courants" (tous départements)
|
|
(date du jour comprise dans la période couverte par le sem.)
|
|
dans lesquels l'utilisateur a la permission ScoView
|
|
(donc tous si le dept du rôle est None).
|
|
|
|
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.
|
|
|
|
"""
|
|
allowed_depts = current_user.get_depts_with_permission(Permission.ScoView)
|
|
date_courante = request.args.get("date_courante")
|
|
if date_courante:
|
|
test_date = datetime.fromisoformat(date_courante)
|
|
else:
|
|
test_date = app.db.func.now()
|
|
etuds = Identite.query.filter(
|
|
Identite.id == FormSemestreInscription.etudid,
|
|
FormSemestreInscription.formsemestre_id == FormSemestre.id,
|
|
FormSemestre.date_debut <= test_date,
|
|
FormSemestre.date_fin >= test_date,
|
|
)
|
|
if not None in allowed_depts:
|
|
# restreint aux départements autorisés:
|
|
etuds = etuds.join(Departement).filter(
|
|
or_(Departement.acronym == acronym for acronym in allowed_depts)
|
|
)
|
|
if long:
|
|
data = [etud.to_dict_api() for etud in etuds]
|
|
else:
|
|
data = [etud.to_dict_short() for etud in etuds]
|
|
return data
|
|
|
|
|
|
@bp.route("/etudiant/etudid/<int:etudid>")
|
|
@bp.route("/etudiant/nip/<string:nip>")
|
|
@bp.route("/etudiant/ine/<string:ine>")
|
|
@api_web_bp.route("/etudiant/etudid/<int:etudid>")
|
|
@api_web_bp.route("/etudiant/nip/<string:nip>")
|
|
@api_web_bp.route("/etudiant/ine/<string:ine>")
|
|
@login_required
|
|
@scodoc
|
|
@permission_required(Permission.ScoView)
|
|
@as_json
|
|
def etudiant(etudid: int = None, nip: str = None, ine: str = None):
|
|
"""
|
|
Retourne les informations de l'étudiant correspondant, ou 404 si non trouvé.
|
|
|
|
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.
|
|
"""
|
|
etud = tools.get_etud(etudid, nip, ine)
|
|
|
|
if etud is None:
|
|
return json_error(
|
|
404,
|
|
message="étudiant inconnu",
|
|
)
|
|
|
|
return etud.to_dict_api()
|
|
|
|
|
|
@bp.route("/etudiant/etudid/<int:etudid>/photo")
|
|
@bp.route("/etudiant/nip/<string:nip>/photo")
|
|
@bp.route("/etudiant/ine/<string:ine>/photo")
|
|
@api_web_bp.route("/etudiant/etudid/<int:etudid>/photo")
|
|
@api_web_bp.route("/etudiant/nip/<string:nip>/photo")
|
|
@api_web_bp.route("/etudiant/ine/<string:ine>/photo")
|
|
@login_required
|
|
@scodoc
|
|
@permission_required(Permission.ScoView)
|
|
def get_photo_image(etudid: int = None, nip: str = None, ine: str = None):
|
|
"""
|
|
Retourne la photo de l'étudiant
|
|
correspondant ou un placeholder si non existant.
|
|
|
|
etudid : l'etudid de l'étudiant
|
|
nip : le code nip de l'étudiant
|
|
ine : le code ine de l'étudiant
|
|
|
|
Attention : Ne peut être qu'utilisée en tant que route de département
|
|
"""
|
|
|
|
etud = tools.get_etud(etudid, nip, ine)
|
|
|
|
if etud is None:
|
|
return json_error(
|
|
404,
|
|
message="étudiant inconnu",
|
|
)
|
|
if not etudid:
|
|
filename = sco_photos.UNKNOWN_IMAGE_PATH
|
|
|
|
size = request.args.get("size", "orig")
|
|
filename = sco_photos.photo_pathname(etud.photo_filename, size=size)
|
|
if not filename:
|
|
filename = sco_photos.UNKNOWN_IMAGE_PATH
|
|
res = sco_photos.build_image_response(filename)
|
|
return res
|
|
|
|
|
|
@bp.route("/etudiants/etudid/<int:etudid>", methods=["GET"])
|
|
@bp.route("/etudiants/nip/<string:nip>", methods=["GET"])
|
|
@bp.route("/etudiants/ine/<string:ine>", methods=["GET"])
|
|
@api_web_bp.route("/etudiants/etudid/<int:etudid>", methods=["GET"])
|
|
@api_web_bp.route("/etudiants/nip/<string:nip>", methods=["GET"])
|
|
@api_web_bp.route("/etudiants/ine/<string:ine>", methods=["GET"])
|
|
@scodoc
|
|
@permission_required(Permission.ScoView)
|
|
@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.
|
|
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.).
|
|
"""
|
|
allowed_depts = current_user.get_depts_with_permission(Permission.ScoView)
|
|
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 json_error(
|
|
404,
|
|
message="parametre manquant",
|
|
)
|
|
if not None in allowed_depts:
|
|
# restreint aux départements autorisés:
|
|
query = query.join(Departement).filter(
|
|
or_(Departement.acronym == acronym for acronym in allowed_depts)
|
|
)
|
|
return [etud.to_dict_api() for etud in query]
|
|
|
|
|
|
@bp.route("/etudiants/name/<string:start>")
|
|
@api_web_bp.route("/etudiants/name/<string:start>")
|
|
@scodoc
|
|
@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.
|
|
La casse et les accents sont ignorés.
|
|
"""
|
|
if len(start) < min_len:
|
|
return []
|
|
start = suppress_accents(start).lower()
|
|
query = Identite.query.filter(
|
|
func.lower(func.unaccent(Identite.nom, type_=VARCHAR)).ilike(start + "%")
|
|
)
|
|
allowed_depts = current_user.get_depts_with_permission(Permission.ScoView)
|
|
if not None in allowed_depts:
|
|
# restreint aux départements autorisés:
|
|
query = query.join(Departement).filter(
|
|
or_(Departement.acronym == acronym for acronym in allowed_depts)
|
|
)
|
|
etuds = query.order_by(Identite.nom, Identite.prenom).limit(limit)
|
|
# Note: on raffine le tri pour les caractères spéciaux et nom usuel ici:
|
|
return [etud.to_dict_api() for etud in sorted(etuds, key=attrgetter("sort_key"))]
|
|
|
|
|
|
@bp.route("/etudiant/etudid/<int:etudid>/formsemestres")
|
|
@bp.route("/etudiant/nip/<string:nip>/formsemestres")
|
|
@bp.route("/etudiant/ine/<string:ine>/formsemestres")
|
|
@api_web_bp.route("/etudiant/etudid/<int:etudid>/formsemestres")
|
|
@api_web_bp.route("/etudiant/nip/<string:nip>/formsemestres")
|
|
@api_web_bp.route("/etudiant/ine/<string:ine>/formsemestres")
|
|
@scodoc
|
|
@permission_required(Permission.ScoView)
|
|
@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.
|
|
Accès par etudid, nip ou ine.
|
|
|
|
Attention, si accès via NIP ou INE, les semestres 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.
|
|
"""
|
|
if etudid is not None:
|
|
q_etud = Identite.query.filter_by(id=etudid)
|
|
elif nip is not None:
|
|
q_etud = Identite.query.filter_by(code_nip=nip)
|
|
elif ine is not None:
|
|
q_etud = Identite.query.filter_by(code_ine=ine)
|
|
else:
|
|
return json_error(404, message="parametre manquant")
|
|
if g.scodoc_dept is not None:
|
|
q_etud = q_etud.filter_by(dept_id=g.scodoc_dept_id)
|
|
etud = q_etud.join(Admission).order_by(desc(Admission.annee)).first()
|
|
if etud is None:
|
|
return json_error(404, message="etudiant inexistant")
|
|
query = FormSemestre.query.filter(
|
|
FormSemestreInscription.etudid == etud.id,
|
|
FormSemestreInscription.formsemestre_id == FormSemestre.id,
|
|
)
|
|
if g.scodoc_dept is not None:
|
|
query = query.filter_by(dept_id=g.scodoc_dept_id)
|
|
|
|
formsemestres = query.order_by(FormSemestre.date_debut)
|
|
|
|
return [formsemestre.to_dict_api() for formsemestre in formsemestres]
|
|
|
|
|
|
@bp.route(
|
|
"/etudiant/<string:code_type>/<string:code>/formsemestre/<int:formsemestre_id>/bulletin",
|
|
)
|
|
@bp.route(
|
|
"/etudiant/<string:code_type>/<string:code>/formsemestre/<int:formsemestre_id>/bulletin/<string:version>",
|
|
)
|
|
@bp.route(
|
|
"/etudiant/<string:code_type>/<string:code>/formsemestre/<int:formsemestre_id>/bulletin/<string:version>/pdf",
|
|
defaults={"pdf": True},
|
|
)
|
|
@bp.route(
|
|
"/etudiant/<string:code_type>/<string:code>/formsemestre/<int:formsemestre_id>/bulletin/<string:version>/pdf/nosig",
|
|
defaults={"pdf": True, "with_img_signatures_pdf": False},
|
|
)
|
|
@api_web_bp.route(
|
|
"/etudiant/<string:code_type>/<string:code>/formsemestre/<int:formsemestre_id>/bulletin",
|
|
)
|
|
@api_web_bp.route(
|
|
"/etudiant/<string:code_type>/<string:code>/formsemestre/<int:formsemestre_id>/bulletin/<string:version>",
|
|
)
|
|
@api_web_bp.route(
|
|
"/etudiant/<string:code_type>/<string:code>/formsemestre/<int:formsemestre_id>/bulletin/<string:version>/pdf",
|
|
defaults={"pdf": True},
|
|
)
|
|
@api_web_bp.route(
|
|
"/etudiant/<string:code_type>/<string:code>/formsemestre/<int:formsemestre_id>/bulletin/<string:version>/pdf/nosig",
|
|
defaults={"pdf": True, "with_img_signatures_pdf": False},
|
|
)
|
|
@scodoc
|
|
@permission_required(Permission.ScoView)
|
|
def bulletin(
|
|
code_type: str = "etudid",
|
|
code: str = None,
|
|
formsemestre_id: int = None,
|
|
version: str = "long",
|
|
pdf: bool = False,
|
|
with_img_signatures_pdf: bool = True,
|
|
):
|
|
"""
|
|
Retourne le bulletin d'un étudiant en fonction de son id et d'un semestre donné
|
|
|
|
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, "long"): short, long, long_mat
|
|
pdf : si spécifié, bulletin au format PDF (et non JSON).
|
|
|
|
Exemple de résultat : voir https://scodoc.org/ScoDoc9API/#bulletin
|
|
"""
|
|
if version == "pdf":
|
|
version = "long"
|
|
pdf = True
|
|
# return f"{code_type}={code}, version={version}, pdf={pdf}"
|
|
formsemestre = FormSemestre.query.filter_by(id=formsemestre_id).first_or_404()
|
|
dept = Departement.query.filter_by(id=formsemestre.dept_id).first_or_404()
|
|
if g.scodoc_dept and dept.acronym != g.scodoc_dept:
|
|
return json_error(404, "formsemestre inexistant", as_response=True)
|
|
app.set_sco_dept(dept.acronym)
|
|
|
|
if code_type == "nip":
|
|
query = Identite.query.filter_by(code_nip=code, dept_id=dept.id)
|
|
elif code_type == "etudid":
|
|
try:
|
|
etudid = int(code)
|
|
except ValueError:
|
|
return json_error(404, "invalid etudid type")
|
|
query = Identite.query.filter_by(id=etudid)
|
|
elif code_type == "ine":
|
|
query = Identite.query.filter_by(code_ine=code, dept_id=dept.id)
|
|
else:
|
|
return json_error(404, "invalid code_type")
|
|
etud = query.first()
|
|
if etud is None:
|
|
return json_error(404, message="etudiant inexistant")
|
|
if pdf:
|
|
pdf_response, _ = do_formsemestre_bulletinetud(
|
|
formsemestre,
|
|
etud,
|
|
version=version,
|
|
format="pdf",
|
|
with_img_signatures_pdf=with_img_signatures_pdf,
|
|
)
|
|
return pdf_response
|
|
return sco_bulletins.get_formsemestre_bulletin_etud_json(
|
|
formsemestre, etud, version=version
|
|
)
|
|
|
|
|
|
@bp.route(
|
|
"/etudiant/etudid/<int:etudid>/formsemestre/<int:formsemestre_id>/groups",
|
|
methods=["GET"],
|
|
)
|
|
@scodoc
|
|
@permission_required(Permission.ScoView)
|
|
@as_json
|
|
def etudiant_groups(formsemestre_id: int, etudid: int = None):
|
|
"""
|
|
Retourne la liste des groupes auxquels appartient l'étudiant dans le formsemestre indiqué
|
|
|
|
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"
|
|
}
|
|
]
|
|
"""
|
|
|
|
query = FormSemestre.query.filter_by(id=formsemestre_id)
|
|
if g.scodoc_dept:
|
|
query = query.filter_by(dept_id=g.scodoc_dept_id)
|
|
formsemestre = query.first()
|
|
if formsemestre is None:
|
|
return json_error(
|
|
404,
|
|
message="formsemestre inconnu",
|
|
)
|
|
dept = formsemestre.departement
|
|
etud = Identite.query.filter_by(id=etudid, dept_id=dept.id).first_or_404(etudid)
|
|
|
|
app.set_sco_dept(dept.acronym)
|
|
data = sco_groups.get_etud_groups(etud.id, formsemestre.id)
|
|
|
|
return data
|