387 lines
11 KiB
Python
387 lines
11 KiB
Python
|
##############################################################################
|
||
|
# ScoDoc
|
||
|
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
||
|
# See LICENSE
|
||
|
##############################################################################
|
||
|
"""ScoDoc 9 API : Assiduités
|
||
|
"""
|
||
|
import os
|
||
|
from datetime import datetime
|
||
|
|
||
|
import app.scodoc.sco_assiduites as scass
|
||
|
import app.scodoc.sco_utils as scu
|
||
|
from app import db
|
||
|
|
||
|
from app.api import api_bp as bp
|
||
|
from app.api import api_web_bp
|
||
|
from app.scodoc.sco_exceptions import ScoValueError
|
||
|
from app.decorators import permission_required, scodoc
|
||
|
from app.models import Identite, Justificatif
|
||
|
from app.scodoc.sco_archives_justificatifs import JustificatifArchiver
|
||
|
from app.scodoc.sco_permissions import Permission
|
||
|
from flask import g, jsonify, request
|
||
|
from flask_login import login_required
|
||
|
from app.scodoc.sco_utils import json_error
|
||
|
|
||
|
|
||
|
# @bp.route("/justificatif/import")
|
||
|
# @api_web_bp.route("/justificatif/import")
|
||
|
# @scodoc
|
||
|
# def justificatif():
|
||
|
# """ """
|
||
|
# archiver: JustificatifArchiver = JustificatifArchiver()
|
||
|
|
||
|
# filename: str = "lol.txt"
|
||
|
# data: bytes = "test".encode("utf-8")
|
||
|
|
||
|
# archiver.save_justificatif(
|
||
|
# etudid=1, filename=filename, data=data, archive_id="2023-02-01-10-29-20"
|
||
|
# )
|
||
|
# return jsonify([filename, "done"])
|
||
|
|
||
|
|
||
|
# @bp.route("/justificatif/remove")
|
||
|
# @api_web_bp.route("/justificatif/remove")
|
||
|
# @scodoc
|
||
|
# def justremove():
|
||
|
# """ """
|
||
|
# archiver: JustificatifArchiver = JustificatifArchiver()
|
||
|
|
||
|
# archiver.delete_justificatif(etudid=1, archive_id="2023-02-01-10-29-20")
|
||
|
# return jsonify("done")
|
||
|
|
||
|
# Partie Modèle
|
||
|
# TODO: justificatif
|
||
|
@bp.route("/justificatif/<int:justif_id>")
|
||
|
@api_web_bp.route("/assiduite/<int:justif_id>")
|
||
|
@scodoc
|
||
|
@permission_required(Permission.ScoView)
|
||
|
def justificatif(justif_id: int = None):
|
||
|
"""Retourne un objet justificatif à partir de son id
|
||
|
|
||
|
Exemple de résultat:
|
||
|
{
|
||
|
"justif_id": 1,
|
||
|
"etudid": 2,
|
||
|
"date_debut": "2022-10-31T08:00+01:00",
|
||
|
"date_fin": "2022-10-31T10:00+01:00",
|
||
|
"etat": "valide",
|
||
|
"fichier": "archive_id",
|
||
|
"raison": "une raison",
|
||
|
"entry_date": "2022-10-31T08:00+01:00",
|
||
|
}
|
||
|
"""
|
||
|
|
||
|
query = Justificatif.query.filter_by(id=justif_id)
|
||
|
if g.scodoc_dept:
|
||
|
query = query.join(Identite).filter_by(dept_id=g.scodoc_dept_id)
|
||
|
justificatif_unique = query.first_or_404()
|
||
|
|
||
|
data = justificatif_unique.to_dict()
|
||
|
|
||
|
return jsonify(_change_etat(data))
|
||
|
|
||
|
|
||
|
# TODO: justificatifs[-query]
|
||
|
@bp.route("/justificatifs/<int:etudid>", defaults={"with_query": False})
|
||
|
@bp.route("/justificatifs/<int:etudid>/query", defaults={"with_query": True})
|
||
|
@api_web_bp.route("/justificatifs/<int:etudid>", defaults={"with_query": False})
|
||
|
@api_web_bp.route("/justificatifs/<int:etudid>/query", defaults={"with_query": True})
|
||
|
@login_required
|
||
|
@scodoc
|
||
|
@permission_required(Permission.ScoView)
|
||
|
def justificatifs(etudid: int = 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
|
||
|
"""
|
||
|
|
||
|
query = Identite.query.filter_by(id=etudid)
|
||
|
if g.scodoc_dept:
|
||
|
query = query.filter_by(dept_id=g.scodoc_dept_id)
|
||
|
|
||
|
etud: Identite = query.first_or_404(etudid)
|
||
|
justificatifs_query = etud.justificatifs
|
||
|
|
||
|
if with_query:
|
||
|
justificatifs_query = _filter_manager(request, justificatifs_query)
|
||
|
|
||
|
data_set: list[dict] = []
|
||
|
for just in justificatifs_query.all():
|
||
|
data = just.to_dict()
|
||
|
data_set.append(_change_etat(data))
|
||
|
|
||
|
return jsonify(data_set)
|
||
|
|
||
|
|
||
|
# TODO: justificatif-create
|
||
|
|
||
|
|
||
|
@bp.route("/justificatif/<int:etudid>/create", methods=["POST"])
|
||
|
@api_web_bp.route("/justificatif/<int:etudid>/create", methods=["POST"])
|
||
|
@scodoc
|
||
|
@login_required
|
||
|
@permission_required(Permission.ScoView)
|
||
|
# @permission_required(Permission.ScoAssiduiteChange)
|
||
|
def justif_create(etudid: int = None):
|
||
|
"""
|
||
|
Création d'un justificatif pour l'étudiant (etudid)
|
||
|
La requête doit avoir un content type "application/json":
|
||
|
[
|
||
|
{
|
||
|
"date_debut": str,
|
||
|
"date_fin": str,
|
||
|
"etat": str,
|
||
|
},
|
||
|
{
|
||
|
"date_debut": str,
|
||
|
"date_fin": str,
|
||
|
"etat": str,
|
||
|
"raison":str,
|
||
|
}
|
||
|
...
|
||
|
]
|
||
|
|
||
|
"""
|
||
|
etud: Identite = Identite.query.filter_by(id=etudid).first_or_404()
|
||
|
|
||
|
create_list: list[object] = request.get_json(force=True)
|
||
|
|
||
|
if not isinstance(create_list, list):
|
||
|
return json_error(404, "Le contenu envoyé n'est pas une liste")
|
||
|
|
||
|
errors: dict[int, str] = {}
|
||
|
success: dict[int, object] = {}
|
||
|
for i, data in enumerate(create_list):
|
||
|
code, obj = _create_singular(data, etud)
|
||
|
if code == 404:
|
||
|
errors[i] = obj
|
||
|
else:
|
||
|
success[i] = obj
|
||
|
|
||
|
return jsonify({"errors": errors, "success": success})
|
||
|
|
||
|
|
||
|
def _create_singular(
|
||
|
data: dict,
|
||
|
etud: Identite,
|
||
|
) -> tuple[int, object]:
|
||
|
errors: list[str] = []
|
||
|
|
||
|
# -- vérifications de l'objet json --
|
||
|
# cas 1 : ETAT
|
||
|
etat = data.get("etat", None)
|
||
|
if etat is None:
|
||
|
errors.append("param 'etat': manquant")
|
||
|
elif etat not in scu.ETATS_JUSTIFICATIF:
|
||
|
errors.append("param 'etat': invalide")
|
||
|
|
||
|
data = _change_etat(data, False)
|
||
|
etat = data.get("etat", None)
|
||
|
|
||
|
# cas 2 : date_debut
|
||
|
date_debut = data.get("date_debut", None)
|
||
|
if date_debut is None:
|
||
|
errors.append("param 'date_debut': manquant")
|
||
|
deb = scu.is_iso_formated(date_debut, convert=True)
|
||
|
if deb is None:
|
||
|
errors.append("param 'date_debut': format invalide")
|
||
|
|
||
|
# cas 3 : date_fin
|
||
|
date_fin = data.get("date_fin", None)
|
||
|
if date_fin is None:
|
||
|
errors.append("param 'date_fin': manquant")
|
||
|
fin = scu.is_iso_formated(date_fin, convert=True)
|
||
|
if fin is None:
|
||
|
errors.append("param 'date_fin': format invalide")
|
||
|
|
||
|
# cas 4 : raison
|
||
|
|
||
|
raison: str = data.get("raison", None)
|
||
|
|
||
|
if errors:
|
||
|
err: str = ", ".join(errors)
|
||
|
return (404, err)
|
||
|
|
||
|
# TOUT EST OK
|
||
|
|
||
|
try:
|
||
|
nouv_justificatif: Justificatif = Justificatif.create_justificatif(
|
||
|
date_debut=deb,
|
||
|
date_fin=fin,
|
||
|
etat=etat,
|
||
|
etud=etud,
|
||
|
raison=raison,
|
||
|
)
|
||
|
|
||
|
db.session.add(nouv_justificatif)
|
||
|
db.session.commit()
|
||
|
return (200, {"justif_id": nouv_justificatif.id})
|
||
|
except ScoValueError as excp:
|
||
|
return (
|
||
|
404,
|
||
|
excp.args[0],
|
||
|
)
|
||
|
|
||
|
|
||
|
# TODO: justificatif-edit
|
||
|
@bp.route("/justificatif/<int:justif_id>/edit", methods=["POST"])
|
||
|
@api_web_bp.route("/justificatif/<int:justif_id>/edit", methods=["POST"])
|
||
|
@login_required
|
||
|
@scodoc
|
||
|
@permission_required(Permission.ScoView)
|
||
|
# @permission_required(Permission.ScoAssiduiteChange)
|
||
|
def justif_edit(justif_id: int):
|
||
|
"""
|
||
|
Edition d'un justificatif à partir de son id
|
||
|
La requête doit avoir un content type "application/json":
|
||
|
{
|
||
|
"etat"?: str,
|
||
|
"raison"?: str
|
||
|
}
|
||
|
"""
|
||
|
justificatif_unique: Justificatif = Justificatif.query.filter_by(
|
||
|
id=justif_id
|
||
|
).first_or_404()
|
||
|
errors: list[str] = []
|
||
|
data = request.get_json(force=True)
|
||
|
|
||
|
# Vérifications de data
|
||
|
|
||
|
# Cas 1 : Etat
|
||
|
if data.get("etat") is not None:
|
||
|
data = _change_etat(data, False)
|
||
|
if data.get("etat") is None:
|
||
|
errors.append("param 'etat': invalide")
|
||
|
else:
|
||
|
justificatif_unique.etat = data.get("etat")
|
||
|
|
||
|
# Cas 2 : raison
|
||
|
raison = data.get("raison", False)
|
||
|
if raison is not False:
|
||
|
justificatif_unique.raison = raison
|
||
|
|
||
|
if errors:
|
||
|
err: str = ", ".join(errors)
|
||
|
return json_error(404, err)
|
||
|
|
||
|
db.session.add(justificatif_unique)
|
||
|
db.session.commit()
|
||
|
return jsonify({"OK": True})
|
||
|
|
||
|
|
||
|
# TODO: justificatif-delete
|
||
|
@bp.route("/justificatif/delete", methods=["POST"])
|
||
|
@api_web_bp.route("/justificatif/delete", methods=["POST"])
|
||
|
@login_required
|
||
|
@scodoc
|
||
|
@permission_required(Permission.ScoView)
|
||
|
# @permission_required(Permission.ScoAssiduiteChange)
|
||
|
def justif_delete():
|
||
|
"""
|
||
|
Suppression d'un justificatif à partir de son id
|
||
|
|
||
|
Forme des données envoyées :
|
||
|
|
||
|
[
|
||
|
<justif_id:int>,
|
||
|
...
|
||
|
]
|
||
|
|
||
|
|
||
|
"""
|
||
|
justificatifs_list: list[int] = request.get_json(force=True)
|
||
|
if not isinstance(justificatifs_list, list):
|
||
|
return json_error(404, "Le contenu envoyé n'est pas une liste")
|
||
|
|
||
|
output = {"errors": {}, "success": {}}
|
||
|
|
||
|
for i, ass in enumerate(justificatifs_list):
|
||
|
code, msg = _delete_singular(ass, db)
|
||
|
if code == 404:
|
||
|
output["errors"][f"{i}"] = msg
|
||
|
else:
|
||
|
output["success"][f"{i}"] = {"OK": True}
|
||
|
db.session.commit()
|
||
|
return jsonify(output)
|
||
|
|
||
|
|
||
|
def _delete_singular(justif_id: int, database):
|
||
|
justificatif_unique: Justificatif = Justificatif.query.filter_by(
|
||
|
id=justif_id
|
||
|
).first()
|
||
|
if justificatif_unique is None:
|
||
|
return (404, "Justificatif non existant")
|
||
|
database.session.delete(justificatif_unique)
|
||
|
return (200, "OK")
|
||
|
|
||
|
|
||
|
# Partie archivage
|
||
|
# TODO: justificatif-import
|
||
|
# TODO: justificatif-export
|
||
|
# TODO: justificatif-remove
|
||
|
# TODO: justificatif-list
|
||
|
|
||
|
# Partie justification
|
||
|
# TODO: justificatif-justified
|
||
|
|
||
|
|
||
|
# -- Utils --
|
||
|
def _change_etat(data: dict, from_int: bool = True):
|
||
|
"""change dans un json la valeur du champs état"""
|
||
|
if from_int:
|
||
|
data["etat"] = scu.ETAT_JUSTIFICATIF_NAME.get(data["etat"])
|
||
|
else:
|
||
|
data["etat"] = scu.ETATS_JUSTIFICATIF.get(data["etat"])
|
||
|
return data
|
||
|
|
||
|
|
||
|
def _filter_manager(requested, justificatifs_query):
|
||
|
"""
|
||
|
Retourne les justificatifs entrés filtrés en fonction de la request
|
||
|
"""
|
||
|
# cas 1 : etat justificatif
|
||
|
etat = requested.args.get("etat")
|
||
|
if etat is not None:
|
||
|
justificatifs_query = scass.filter_justificatifs_by_etat(
|
||
|
justificatifs_query, etat
|
||
|
)
|
||
|
|
||
|
# cas 2 : date de début
|
||
|
deb = requested.args.get("date_debut")
|
||
|
deb: datetime = scu.is_iso_formated(deb, True)
|
||
|
if deb is not None:
|
||
|
|
||
|
justificatifs_query = scass.filter_justificatifs_by_date(
|
||
|
justificatifs_query, deb, sup=True
|
||
|
)
|
||
|
|
||
|
# cas 3 : date de fin
|
||
|
fin = requested.args.get("date_fin")
|
||
|
fin = scu.is_iso_formated(fin, True)
|
||
|
|
||
|
if fin is not None:
|
||
|
justificatifs_query = scass.filter_justificatifs_by_date(
|
||
|
justificatifs_query, fin, sup=False
|
||
|
)
|
||
|
|
||
|
return justificatifs_query
|