310 lines
8.8 KiB
Python
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
|