ScoDoc-PE/app/api/assiduites.py

310 lines
8.8 KiB
Python

##############################################################################
# ScoDoc
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
# See LICENSE
##############################################################################
"""ScoDoc 9 API : Assiduités
"""
from datetime import datetime
from pytz import UTC
from typing import List
from flask import g, jsonify, request
from app import db
from app.api import api_bp as bp
from app.scodoc.sco_utils import json_error
from app.decorators import scodoc, permission_required
from app.scodoc.sco_permissions import Permission
from flask_login import login_required
from app.models import Identite, Assiduite
import app.scodoc.sco_utils as scu
@bp.route("/assiduite/<int:assiduiteid>")
@scodoc
@permission_required(Permission.ScoView)
def assiduite(assiduiteid: int = None):
"""Retourne un objet assiduité à partir de son id
Exemple de résultat:
{
"assiduiteid": 1,
"etuid": 2,
"moduleimpl_id": 3,
"date_debut": "2022-10-31T08:00",
"date_fin": "2022-10-31T10:00",
"etat": "retard"
}
"""
assiduite = Assiduite.query.get(assiduiteid)
if assiduite is None:
return json_error(404, message="assiduité inexistante")
data = assiduite.to_dict()
return jsonify(change_etat(data))
@bp.route("/assiduites/<int:etuid>", defaults={"with_query": False})
@bp.route("/assiduites/<int:etuid>/query", defaults={"with_query": True})
@login_required
@scodoc
@permission_required(Permission.ScoView)
def assiduites(etuid: int = None, with_query: bool = False):
"""Retourne toutes les assiduités d'un étudiant"""
query = Identite.query.filter_by(id=etuid)
if g.scodoc_dept:
query = query.filter_by(dept_id=g.scodoc_dept_id)
etud: Identite = query.first_or_404(etuid)
assiduites: List[Assiduite] = etud.assiduites.all()
if with_query:
# cas 1 : etat assiduite
etat = request.args.get("etat")
if etat is not None:
etat = list(etat.split(","))
etat = [scu.ETATS_ASSIDUITE.get(e, "absent") for e in etat]
assiduites = [ass for ass in assiduites if ass.etat in etat]
# cas 2 : date de début
deb = request.args.get("date_debut")
deb: datetime = is_iso_formated(deb, True)
if deb is not None:
filtered_assiduites = []
for ass in assiduites:
if deb.tzinfo is None:
deb: datetime = deb.replace(tzinfo=ass.date_debut.tzinfo)
if ass.date_debut >= deb:
filtered_assiduites.append(ass)
assiduites.clear()
assiduites.extend(filtered_assiduites)
# cas 3 : date de fin
fin = request.args.get("date_fin")
fin = is_iso_formated(fin, True)
if fin is not None:
filtered_assiduites = []
for ass in assiduites:
if fin.tzinfo is None:
fin: datetime = fin.replace(tzinfo=ass.date_fin.tzinfo)
if ass.date_fin <= fin:
filtered_assiduites.append(ass)
assiduites.clear()
assiduites.extend(filtered_assiduites)
# cas 4 : moduleimpl_id
module = request.args.get("moduleimpl_id")
try:
module = int(module)
except Exception:
module = None
if module is not None:
assiduites = [ass for ass in assiduites if ass.moduleimpl_id == module]
data_set: List[dict] = []
for ass in assiduites:
data = ass.to_dict()
data_set.append(change_etat(data))
return jsonify(data_set)
@bp.route("/assiduite/<int:etuid>/create", methods=["POST"])
@scodoc
@permission_required(Permission.ScoView)
def create(etuid: int = None):
"""
Création d'une assiduité pour l'étudiant (etuid)
La requête doit avoir un content type "application/json":
{
"date_debut": str,
"date_fin": str,
"etat": str,
}
ou
{
"date_debut": str,
"date_fin": str,
"etat": str,
"moduleimpl_id": int,
}
"""
etud: Identite = Identite.query.filter_by(id=etuid).first_or_404()
data = request.get_json(force=True)
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.keys():
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 = is_iso_formated(date_debut, 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 = is_iso_formated(date_fin, True)
if fin is None:
errors.append(f"param 'date_fin': format invalide")
# cas 4 : moduleimpl_id
moduleimpl_id = data.get("moduleimpl_id", None)
if moduleimpl_id is not None:
try:
moduleimpl_id: int = int(moduleimpl_id)
if moduleimpl_id < 0:
raise Exception
except:
errors.append("param 'moduleimpl_id': invalide")
if errors != []:
err: str = ", ".join(errors)
return json_error(404, err)
# TOUT EST OK
nouv_assiduite: Assiduite or str = Assiduite.create_assiduite(
date_debut=deb,
date_fin=fin,
etat=etat,
etud=etud,
module=moduleimpl_id,
)
if type(nouv_assiduite) is Assiduite:
return jsonify({"assiduiteid": nouv_assiduite.assiduiteid})
return json_error(
404,
{
1: "La période sélectionnée est déjà couverte par une autre assiduite",
2: "L'étudiant ne participe pas au moduleimpl sélectionné",
}.get(nouv_assiduite),
)
@bp.route("/assiduite/<int:assiduiteid>/delete", methods=["POST"])
@login_required
@scodoc
@permission_required(Permission.ScoAssiduiteChange)
def delete(assiduiteid: int):
"""
Suppression d'une assiduité à partir de son id
"""
assiduite: Assiduite = Assiduite.query.filter_by(id=assiduiteid).first_or_404()
db.session.delete(assiduite)
db.session.commit()
return jsonify({"OK": True})
@bp.route("/assiduite/<int:assiduiteid>/edit", methods=["POST"])
@login_required
@scodoc
@permission_required(Permission.ScoAssiduiteChange)
def edit(assiduiteid: int):
"""
Edition d'une assiduité à partir de son id
La requête doit avoir un content type "application/json":
{
"etat": str,
"moduleimpl_id": int
}
"""
assiduite: Assiduite = Assiduite.query.filter_by(id=assiduiteid).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.etat = data.get("etat")
# Cas 2 : Moduleimpl_id
moduleimpl_id = data.get("moduleimpl_id", False)
if moduleimpl_id is not False:
try:
if moduleimpl_id is not None:
moduleimpl_id: int = int(moduleimpl_id)
if moduleimpl_id < 0 or not Assiduite.verif_moduleimpl(
moduleimpl_id, assiduite.etudid
):
raise Exception
assiduite.moduleimpl_id = moduleimpl_id
except:
errors.append("param 'moduleimpl_id': invalide")
if errors != []:
err: str = ", ".join(errors)
return json_error(404, err)
db.session.add(assiduite)
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 is_iso_formated(date: str, convert=False) -> bool or datetime or None:
"""
Vérifie si une date est au format iso
Retourne un booléen Vrai (ou un objet Datetime si convert = True)
si l'objet est au format iso
Retourne Faux si l'objet n'est pas au format et convert = False
Retourne None sinon
"""
import dateutil.parser as dtparser
try:
date: datetime = dtparser.isoparse(date)
if date.tzinfo is None:
date = UTC.localize(date)
return date if convert else True
except Exception:
return None if convert else False