595 lines
18 KiB
Python
595 lines
18 KiB
Python
##############################################################################
|
|
# ScoDoc
|
|
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
|
# See LICENSE
|
|
##############################################################################
|
|
"""ScoDoc 9 API : Assiduités
|
|
"""
|
|
from datetime import datetime
|
|
from typing import List
|
|
from flask import g, jsonify, request
|
|
from flask_login import login_required
|
|
|
|
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.decorators import permission_required, scodoc
|
|
from app.models import Assiduite, FormSemestre, Identite, ModuleImpl
|
|
from app.scodoc.sco_exceptions import ScoValueError
|
|
from app.scodoc.sco_permissions import Permission
|
|
from app.scodoc.sco_utils import json_error
|
|
|
|
|
|
@bp.route("/assiduite/<int:assiduite_id>")
|
|
@api_web_bp.route("/assiduite/<int:assiduite_id>")
|
|
@scodoc
|
|
@permission_required(Permission.ScoView)
|
|
def assiduite(assiduite_id: int = None):
|
|
"""Retourne un objet assiduité à partir de son id
|
|
|
|
Exemple de résultat:
|
|
{
|
|
"assiduite_id": 1,
|
|
"etudid": 2,
|
|
"moduleimpl_id": 3,
|
|
"date_debut": "2022-10-31T08:00+01:00",
|
|
"date_fin": "2022-10-31T10:00+01:00",
|
|
"etat": "retard",
|
|
"desc": "une description",
|
|
}
|
|
"""
|
|
|
|
query = Assiduite.query.filter_by(id=assiduite_id)
|
|
# if g.scodoc_dept:
|
|
# query = query.join(Identite).filter_by(dept_id=g.scodoc_dept_id)
|
|
assiduite_query = query.first_or_404()
|
|
|
|
data = assiduite_query.to_dict()
|
|
|
|
return jsonify(change_etat(data))
|
|
|
|
|
|
@bp.route("/assiduites/<int:etudid>/count", defaults={"with_query": False})
|
|
@bp.route("/assiduites/<int:etudid>/count/query", defaults={"with_query": True})
|
|
@api_web_bp.route("/assiduites/<int:etudid>/count", defaults={"with_query": False})
|
|
@api_web_bp.route("/assiduites/<int:etudid>/count/query", defaults={"with_query": True})
|
|
@login_required
|
|
@scodoc
|
|
@permission_required(Permission.ScoView)
|
|
def count_assiduites(etudid: int = 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?formsemstre_id=[int]
|
|
ex query?formsemestre_id=3
|
|
|
|
|
|
"""
|
|
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)
|
|
filtered: dict[str, object] = {}
|
|
metric: str = "all"
|
|
|
|
if with_query:
|
|
metric, filtered = count_manager(request)
|
|
|
|
return jsonify(
|
|
scass.get_assiduites_stats(
|
|
assiduites=etud.assiduites, metric=metric, filtered=filtered
|
|
)
|
|
)
|
|
|
|
|
|
@bp.route("/assiduites/<int:etudid>", defaults={"with_query": False})
|
|
@bp.route("/assiduites/<int:etudid>/query", defaults={"with_query": True})
|
|
@api_web_bp.route("/assiduites/<int:etudid>", defaults={"with_query": False})
|
|
@api_web_bp.route("/assiduites/<int:etudid>/query", defaults={"with_query": True})
|
|
@login_required
|
|
@scodoc
|
|
@permission_required(Permission.ScoView)
|
|
def assiduites(etudid: int = 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
|
|
|
|
|
|
"""
|
|
|
|
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)
|
|
assiduites_query = etud.assiduites
|
|
|
|
if with_query:
|
|
assiduites_query = filter_manager(request, assiduites_query)
|
|
|
|
data_set: List[dict] = []
|
|
for ass in assiduites_query.all():
|
|
data = ass.to_dict()
|
|
data_set.append(change_etat(data))
|
|
|
|
return jsonify(data_set)
|
|
|
|
|
|
@bp.route(
|
|
"/assiduites/formsemestre/<int:formsemestre_id>", defaults={"with_query": False}
|
|
)
|
|
@api_web_bp.route(
|
|
"/assiduites/formsemestre/<int:formsemestre_id>", defaults={"with_query": False}
|
|
)
|
|
@bp.route(
|
|
"/assiduites/formsemestre/<int:formsemestre_id>/query",
|
|
defaults={"with_query": True},
|
|
)
|
|
@api_web_bp.route(
|
|
"/assiduites/formsemestre/<int:formsemestre_id>/query",
|
|
defaults={"with_query": True},
|
|
)
|
|
@login_required
|
|
@scodoc
|
|
@permission_required(Permission.ScoView)
|
|
def assiduites_formsemestre(formsemestre_id: int, with_query: bool = False):
|
|
formsemestre: FormSemestre = None
|
|
formsemestre_id = int(formsemestre_id)
|
|
formsemestre = FormSemestre.query.filter_by(id=formsemestre_id).first()
|
|
|
|
if formsemestre is None:
|
|
return json_error(404, "le paramètre 'formsemestre_id' n'existe pas")
|
|
|
|
assiduites_query = scass.filter_by_formsemestre(Assiduite.query, formsemestre)
|
|
|
|
if with_query:
|
|
assiduites_query = filter_manager(request, assiduites_query)
|
|
|
|
data_set: List[dict] = []
|
|
for ass in assiduites_query.all():
|
|
data = ass.to_dict()
|
|
data_set.append(change_etat(data))
|
|
|
|
return jsonify(data_set)
|
|
|
|
|
|
@bp.route(
|
|
"/assiduites/formsemestre/<int:formsemestre_id>/count",
|
|
defaults={"with_query": False},
|
|
)
|
|
@api_web_bp.route(
|
|
"/assiduites/formsemestre/<int:formsemestre_id>/count",
|
|
defaults={"with_query": False},
|
|
)
|
|
@bp.route(
|
|
"/assiduites/formsemestre/<int:formsemestre_id>/count/query",
|
|
defaults={"with_query": True},
|
|
)
|
|
@api_web_bp.route(
|
|
"/assiduites/formsemestre/<int:formsemestre_id>/count/query",
|
|
defaults={"with_query": True},
|
|
)
|
|
@login_required
|
|
@scodoc
|
|
@permission_required(Permission.ScoView)
|
|
def count_assiduites_formsemestre(
|
|
formsemestre_id: int = None, with_query: bool = False
|
|
):
|
|
formsemestre: FormSemestre = None
|
|
formsemestre_id = int(formsemestre_id)
|
|
formsemestre = FormSemestre.query.filter_by(id=formsemestre_id).first()
|
|
|
|
if formsemestre is None:
|
|
return json_error(404, "le paramètre 'formsemestre_id' n'existe pas")
|
|
|
|
etuds = formsemestre.etuds.all()
|
|
etuds_id = [etud.id for etud in etuds]
|
|
|
|
assiduites_query = Assiduite.query.filter(Assiduite.etudid.in_(etuds_id))
|
|
assiduites_query = scass.filter_by_formsemestre(assiduites_query, formsemestre)
|
|
metric: str = "all"
|
|
filtered: dict = {}
|
|
if with_query:
|
|
metric, filtered = count_manager(request)
|
|
|
|
return jsonify(scass.get_assiduites_stats(assiduites_query, metric, filtered))
|
|
|
|
|
|
@bp.route("/assiduite/<int:etudid>/create", methods=["POST"])
|
|
@api_web_bp.route("/assiduite/<int:etudid>/create", methods=["POST"])
|
|
@scodoc
|
|
@login_required
|
|
@permission_required(Permission.ScoView)
|
|
# @permission_required(Permission.ScoAssiduiteChange)
|
|
def create(etudid: int = None):
|
|
"""
|
|
Création d'une assiduité 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,
|
|
"moduleimpl_id": int,
|
|
"desc":str,
|
|
}
|
|
...
|
|
]
|
|
|
|
TODO:
|
|
- vérifier si l'entrée est bien une liste
|
|
|
|
"""
|
|
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_ASSIDUITE:
|
|
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 : moduleimpl_id
|
|
|
|
moduleimpl_id = data.get("moduleimpl_id", False)
|
|
moduleimpl: ModuleImpl = None
|
|
|
|
if moduleimpl_id is not False:
|
|
moduleimpl = ModuleImpl.query.filter_by(id=int(moduleimpl_id)).first()
|
|
if moduleimpl is None:
|
|
errors.append("param 'moduleimpl_id': invalide")
|
|
|
|
# cas 5 : desc
|
|
|
|
desc: str = data.get("desc", None)
|
|
|
|
if errors:
|
|
err: str = ", ".join(errors)
|
|
return (404, err)
|
|
|
|
# TOUT EST OK
|
|
|
|
try:
|
|
nouv_assiduite: Assiduite = Assiduite.create_assiduite(
|
|
date_debut=deb,
|
|
date_fin=fin,
|
|
etat=etat,
|
|
etud=etud,
|
|
moduleimpl=moduleimpl,
|
|
description=desc,
|
|
)
|
|
|
|
db.session.add(nouv_assiduite)
|
|
db.session.commit()
|
|
return (200, {"assiduite_id": nouv_assiduite.assiduite_id})
|
|
except ScoValueError as excp:
|
|
return (
|
|
404,
|
|
excp.args[0],
|
|
)
|
|
|
|
|
|
@bp.route("/assiduite/delete", methods=["POST"])
|
|
@api_web_bp.route("/assiduite/delete", methods=["POST"])
|
|
@login_required
|
|
@scodoc
|
|
@permission_required(Permission.ScoView)
|
|
# @permission_required(Permission.ScoAssiduiteChange)
|
|
def delete():
|
|
"""
|
|
Suppression d'une assiduité à partir de son id
|
|
|
|
Forme des données envoyées :
|
|
|
|
[
|
|
<assiduite_id:int>,
|
|
...
|
|
]
|
|
|
|
|
|
"""
|
|
assiduites_list: list[int] = request.get_json(force=True)
|
|
if not isinstance(assiduites_list, list):
|
|
return json_error(404, "Le contenu envoyé n'est pas une liste")
|
|
|
|
output = {"errors": {}, "success": {}}
|
|
|
|
for i, ass in enumerate(assiduites_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(assiduite_id: int, database):
|
|
assiduite_unique: Assiduite = Assiduite.query.filter_by(id=assiduite_id).first()
|
|
if assiduite_unique is None:
|
|
return (404, "Assiduite non existante")
|
|
database.session.delete(assiduite_unique)
|
|
return (200, "OK")
|
|
|
|
|
|
@bp.route("/assiduite/<int:assiduite_id>/edit", methods=["POST"])
|
|
@api_web_bp.route("/assiduite/<int:assiduite_id>/edit", methods=["POST"])
|
|
@login_required
|
|
@scodoc
|
|
@permission_required(Permission.ScoView)
|
|
# @permission_required(Permission.ScoAssiduiteChange)
|
|
def edit(assiduite_id: int):
|
|
"""
|
|
Edition d'une assiduité à partir de son id
|
|
La requête doit avoir un content type "application/json":
|
|
{
|
|
"etat"?: str,
|
|
"moduleimpl_id"?: int
|
|
"desc"?: str
|
|
}
|
|
"""
|
|
assiduite_unique: Assiduite = Assiduite.query.filter_by(
|
|
id=assiduite_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:
|
|
assiduite_unique.etat = data.get("etat")
|
|
|
|
# Cas 2 : Moduleimpl_id
|
|
moduleimpl_id = data.get("moduleimpl_id", False)
|
|
moduleimpl: ModuleImpl = None
|
|
|
|
if moduleimpl_id is not False:
|
|
if moduleimpl_id is not None:
|
|
moduleimpl = ModuleImpl.query.filter_by(id=int(moduleimpl_id)).first()
|
|
if moduleimpl is None:
|
|
errors.append("param 'moduleimpl_id': invalide")
|
|
else:
|
|
if not moduleimpl.est_inscrit(
|
|
Identite.query.filter_by(id=assiduite_unique.etudid).first()
|
|
):
|
|
errors.append("param 'moduleimpl_id': etud non inscrit")
|
|
else:
|
|
assiduite_unique.moduleimpl_id = moduleimpl_id
|
|
else:
|
|
assiduite_unique.moduleimpl_id = moduleimpl_id
|
|
|
|
# Cas 3 : desc
|
|
desc = data.get("desc", False)
|
|
if desc is not False:
|
|
assiduite_unique.desc = desc
|
|
|
|
if errors:
|
|
err: str = ", ".join(errors)
|
|
return json_error(404, err)
|
|
|
|
db.session.add(assiduite_unique)
|
|
db.session.commit()
|
|
return jsonify({"OK": True})
|
|
|
|
|
|
# -- 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_ASSIDUITE_NAME.get(data["etat"])
|
|
else:
|
|
data["etat"] = scu.ETATS_ASSIDUITE.get(data["etat"])
|
|
return data
|
|
|
|
|
|
def count_manager(requested) -> tuple[str, dict]:
|
|
"""
|
|
Retourne la/les métriques à utiliser ainsi que le filtre donnés en query de la requête
|
|
"""
|
|
filtered: dict = {}
|
|
# cas 1 : etat assiduite
|
|
etat = requested.args.get("etat")
|
|
if etat is not None:
|
|
filtered["etat"] = 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:
|
|
filtered["date_debut"] = deb
|
|
|
|
# cas 3 : date de fin
|
|
fin = requested.args.get("date_fin")
|
|
fin = scu.is_iso_formated(fin, True)
|
|
|
|
if fin is not None:
|
|
filtered["date_fin"] = fin
|
|
|
|
# cas 4 : moduleimpl_id
|
|
module = requested.args.get("moduleimpl_id", False)
|
|
try:
|
|
if module is False:
|
|
raise ValueError
|
|
if module != "":
|
|
module = int(module)
|
|
else:
|
|
module = None
|
|
except ValueError:
|
|
module = False
|
|
|
|
if module is not False:
|
|
filtered["moduleimpl_id"] = module
|
|
|
|
# cas 5 : formsemestre_id
|
|
formsemestre_id = requested.args.get("formsemestre_id")
|
|
|
|
if formsemestre_id is not None:
|
|
formsemestre: FormSemestre = None
|
|
formsemestre_id = int(formsemestre_id)
|
|
formsemestre = FormSemestre.query.filter_by(id=formsemestre_id).first()
|
|
filtered["formsemestre"] = formsemestre
|
|
|
|
# cas 6 : type
|
|
metric = requested.args.get("metric", "all")
|
|
|
|
return (metric, filtered)
|
|
|
|
|
|
def filter_manager(requested, assiduites_query):
|
|
"""
|
|
Retourne les assiduites entrées filtrées en fonction de la request
|
|
"""
|
|
# cas 1 : etat assiduite
|
|
etat = requested.args.get("etat")
|
|
if etat is not None:
|
|
assiduites_query = scass.filter_by_etat(assiduites_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:
|
|
|
|
assiduites_query = scass.filter_by_date(assiduites_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:
|
|
assiduites_query = scass.filter_by_date(assiduites_query, fin, sup=False)
|
|
|
|
# cas 4 : moduleimpl_id
|
|
module = requested.args.get("moduleimpl_id", False)
|
|
try:
|
|
if module is False:
|
|
raise ValueError
|
|
if module != "":
|
|
module = int(module)
|
|
else:
|
|
module = None
|
|
except ValueError:
|
|
module = False
|
|
|
|
if module is not False:
|
|
assiduites_query = scass.filter_by_module_impl(assiduites_query, module)
|
|
|
|
# cas 5 : formsemestre_id
|
|
formsemestre_id = requested.args.get("formsemestre_id")
|
|
|
|
if formsemestre_id is not None:
|
|
formsemestre: FormSemestre = None
|
|
formsemestre_id = int(formsemestre_id)
|
|
formsemestre = FormSemestre.query.filter_by(id=formsemestre_id).first()
|
|
assiduites_query = scass.filter_by_formsemestre(assiduites_query, formsemestre)
|
|
|
|
return assiduites_query
|