API Assiduites :
- Comptage - Filtrage par Formsemestre - Envoi en lot (create / delete)
This commit is contained in:
parent
0a1a847044
commit
3ff4abd19c
@ -6,25 +6,33 @@
|
||||
"""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.api import api_bp as bp, api_web_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
|
||||
from app.models import Identite, Assiduite, FormSemestreInscription, FormSemestre
|
||||
import app.scodoc.sco_utils as scu
|
||||
import app.scodoc.sco_assiduites as scass
|
||||
|
||||
|
||||
"""
|
||||
TODO:
|
||||
- Faire une nouvelle route pour obtenir toutes les assiduites d'un formsemstre
|
||||
- Ajouter des variantes "batch" pour chaque route POST pour envoyer des listes d'objets
|
||||
"""
|
||||
|
||||
|
||||
@bp.route("/assiduite/<int:assiduiteid>")
|
||||
@api_web_bp.route("/assiduite/<int:assiduiteid>")
|
||||
@scodoc
|
||||
@permission_required(Permission.ScoView)
|
||||
def assiduite(assiduiteid: int = None):
|
||||
@ -33,10 +41,10 @@ def assiduite(assiduiteid: int = None):
|
||||
Exemple de résultat:
|
||||
{
|
||||
"assiduiteid": 1,
|
||||
"etuid": 2,
|
||||
"etudid": 2,
|
||||
"moduleimpl_id": 3,
|
||||
"date_debut": "2022-10-31T08:00",
|
||||
"date_fin": "2022-10-31T10:00",
|
||||
"date_debut": "2022-10-31T08:00+01:00",
|
||||
"date_fin": "2022-10-31T10:00+01:00",
|
||||
"etat": "retard"
|
||||
}
|
||||
"""
|
||||
@ -50,82 +58,250 @@ def assiduite(assiduiteid: int = None):
|
||||
return jsonify(change_etat(data))
|
||||
|
||||
|
||||
@bp.route("/assiduites/<int:etuid>", defaults={"with_query": False})
|
||||
@bp.route("/assiduites/<int:etuid>/query", defaults={"with_query": True})
|
||||
@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 assiduites(etuid: int = None, with_query: bool = False):
|
||||
"""Retourne toutes les assiduités d'un étudiant"""
|
||||
query = Identite.query.filter_by(id=etuid)
|
||||
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(etuid)
|
||||
assiduites: List[Assiduite] = etud.assiduites.all()
|
||||
etud: Identite = query.first_or_404(etudid)
|
||||
filter: dict[str, object] = {}
|
||||
metric: str = "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]
|
||||
filter["etat"] = etat
|
||||
|
||||
# cas 2 : date de début
|
||||
deb = request.args.get("date_debut")
|
||||
deb: datetime = is_iso_formated(deb, True)
|
||||
|
||||
deb: datetime = scu.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)
|
||||
filter["date_debut"] = deb
|
||||
|
||||
# cas 3 : date de fin
|
||||
fin = request.args.get("date_fin")
|
||||
fin = is_iso_formated(fin, True)
|
||||
fin = scu.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)
|
||||
filter["date_fin"] = fin
|
||||
|
||||
# cas 4 : moduleimpl_id
|
||||
module = request.args.get("moduleimpl_id")
|
||||
module = request.args.get("moduleimpl_id", False)
|
||||
try:
|
||||
module = int(module)
|
||||
if module is False:
|
||||
raise Exception
|
||||
if module != "":
|
||||
module = int(module)
|
||||
else:
|
||||
module = None
|
||||
except Exception:
|
||||
module = None
|
||||
module = False
|
||||
|
||||
if module is not None:
|
||||
assiduites = [ass for ass in assiduites if ass.moduleimpl_id == module]
|
||||
if module is not False:
|
||||
filter["moduleimpl_id"] = module
|
||||
|
||||
# cas 5 : formsemestre_id
|
||||
formsemestre_id = request.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()
|
||||
filter["formsemestre"] = formsemestre
|
||||
|
||||
# cas 6 : type
|
||||
metric = request.args.get("metric", "count")
|
||||
|
||||
return jsonify(scass.get_assiduite_stats(etud=etud, metric=metric, filter=filter))
|
||||
|
||||
|
||||
@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=
|
||||
|
||||
|
||||
"""
|
||||
|
||||
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 = etud.assiduites
|
||||
|
||||
if with_query:
|
||||
# cas 1 : etat assiduite
|
||||
etat = request.args.get("etat")
|
||||
if etat is not None:
|
||||
assiduites = scass.filter_by_etat(assiduites, etat)
|
||||
|
||||
# cas 2 : date de début
|
||||
deb = request.args.get("date_debut")
|
||||
deb: datetime = scu.is_iso_formated(deb, True)
|
||||
if deb is not None:
|
||||
|
||||
assiduites = scass.filter_by_date(assiduites, deb, sup=True)
|
||||
|
||||
# cas 3 : date de fin
|
||||
fin = request.args.get("date_fin")
|
||||
fin = scu.is_iso_formated(fin, True)
|
||||
|
||||
if fin is not None:
|
||||
assiduites = scass.filter_by_date(assiduites, fin, sup=False)
|
||||
|
||||
# cas 4 : moduleimpl_id
|
||||
module = request.args.get("moduleimpl_id", False)
|
||||
try:
|
||||
if module is False:
|
||||
raise Exception
|
||||
if module != "":
|
||||
module = int(module)
|
||||
else:
|
||||
module = None
|
||||
except Exception:
|
||||
module = False
|
||||
|
||||
if module is not False:
|
||||
assiduites = scass.filter_by_module_impl(assiduites, module)
|
||||
|
||||
# cas 5 : formsemestre_id
|
||||
formsemestre_id = request.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 = scass.filter_by_formsemstre(assiduites, formsemestre)
|
||||
|
||||
data_set: List[dict] = []
|
||||
for ass in assiduites:
|
||||
for ass in assiduites.all():
|
||||
data = ass.to_dict()
|
||||
data_set.append(change_etat(data))
|
||||
|
||||
return jsonify(data_set)
|
||||
|
||||
|
||||
@bp.route("/assiduite/<int:etuid>/create", methods=["POST"])
|
||||
@bp.route("/assiduites/formsemestre/<int:formsemestre_id>")
|
||||
@api_web_bp.route("/assiduites/formsemestre/<int:formsemestre_id>")
|
||||
@login_required
|
||||
@scodoc
|
||||
@permission_required(Permission.ScoView)
|
||||
def create(etuid: int = None):
|
||||
def assiduites_formsemestre(formsemestre_id: int = None):
|
||||
if formsemestre_id is not None:
|
||||
formsemestre: FormSemestre = None
|
||||
formsemestre_id = int(formsemestre_id)
|
||||
formsemestre = FormSemestre.query.filter_by(id=formsemestre_id).first()
|
||||
|
||||
if formsemestre is None:
|
||||
json_error(404, "le paramètre 'formsemestre_id' n'existe pas")
|
||||
|
||||
etuds = formsemestre.etuds.all()
|
||||
assiduites = []
|
||||
for etud in etuds:
|
||||
assiduites.extend(
|
||||
scass.filter_by_formsemstre(etud.assiduites, formsemestre).all()
|
||||
)
|
||||
|
||||
data_set: List[dict] = []
|
||||
for ass in assiduites:
|
||||
data = ass.to_dict()
|
||||
data_set.append(change_etat(data))
|
||||
|
||||
return jsonify(data_set)
|
||||
|
||||
else:
|
||||
return json_error(404, "le paramètre 'formsemestre_id doit être renseigné'")
|
||||
|
||||
|
||||
@bp.route("/assiduite/<int:etudid>/create", methods=["POST"], defaults={"batch": False})
|
||||
@api_web_bp.route(
|
||||
"/assiduite/<int:etudid>/create", methods=["POST"], defaults={"batch": False}
|
||||
)
|
||||
@bp.route(
|
||||
"/assiduite/<int:etudid>/create/batch", methods=["POST"], defaults={"batch": True}
|
||||
)
|
||||
@api_web_bp.route(
|
||||
"/assiduite/<int:etudid>/create/batch", methods=["POST"], defaults={"batch": True}
|
||||
)
|
||||
@scodoc
|
||||
@permission_required(Permission.ScoView)
|
||||
def create(etudid: int = None, batch: bool = False):
|
||||
"""
|
||||
Création d'une assiduité pour l'étudiant (etuid)
|
||||
Création d'une assiduité pour l'étudiant (etudid)
|
||||
La requête doit avoir un content type "application/json":
|
||||
{
|
||||
"date_debut": str,
|
||||
@ -142,9 +318,33 @@ def create(etuid: int = None):
|
||||
|
||||
|
||||
"""
|
||||
etud: Identite = Identite.query.filter_by(id=etuid).first_or_404()
|
||||
etud: Identite = Identite.query.filter_by(id=etudid).first_or_404()
|
||||
if batch:
|
||||
errors: dict[int, str] = {}
|
||||
success: dict[
|
||||
int,
|
||||
] = {}
|
||||
for i, data in enumerate(request.get_json(force=True).get("batch")):
|
||||
code, obj = create_singular(data, etud)
|
||||
if code == 404:
|
||||
errors[i] = obj
|
||||
else:
|
||||
success[i] = obj
|
||||
|
||||
data = request.get_json(force=True)
|
||||
return jsonify({"errors": errors, "success": success})
|
||||
|
||||
else:
|
||||
code, obj = create_singular(request.get_json(force=True), etud)
|
||||
if code == 404:
|
||||
return json_error(code, obj)
|
||||
else:
|
||||
return jsonify(obj)
|
||||
|
||||
|
||||
def create_singular(
|
||||
data: dict,
|
||||
etud: Identite,
|
||||
) -> tuple[int, object]:
|
||||
errors: List[str] = []
|
||||
|
||||
# -- vérifications de l'objet json --
|
||||
@ -162,7 +362,7 @@ def create(etuid: int = None):
|
||||
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)
|
||||
deb = scu.is_iso_formated(date_debut, convert=True)
|
||||
if deb is None:
|
||||
errors.append("param 'date_debut': format invalide")
|
||||
|
||||
@ -170,7 +370,7 @@ def create(etuid: int = None):
|
||||
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)
|
||||
fin = scu.is_iso_formated(date_fin, convert=True)
|
||||
if fin is None:
|
||||
errors.append(f"param 'date_fin': format invalide")
|
||||
|
||||
@ -187,10 +387,10 @@ def create(etuid: int = None):
|
||||
|
||||
if errors != []:
|
||||
err: str = ", ".join(errors)
|
||||
return json_error(404, err)
|
||||
return (404, err)
|
||||
|
||||
# TOUT EST OK
|
||||
nouv_assiduite: Assiduite or str = Assiduite.create_assiduite(
|
||||
nouv_assiduite: Assiduite or int = Assiduite.create_assiduite(
|
||||
date_debut=deb,
|
||||
date_fin=fin,
|
||||
etat=etat,
|
||||
@ -199,9 +399,11 @@ def create(etuid: int = None):
|
||||
)
|
||||
|
||||
if type(nouv_assiduite) is Assiduite:
|
||||
return jsonify({"assiduiteid": nouv_assiduite.assiduiteid})
|
||||
db.session.add(nouv_assiduite)
|
||||
db.session.commit()
|
||||
return (200, {"assiduiteid": nouv_assiduite.assiduiteid})
|
||||
|
||||
return json_error(
|
||||
return (
|
||||
404,
|
||||
{
|
||||
1: "La période sélectionnée est déjà couverte par une autre assiduite",
|
||||
@ -210,25 +412,74 @@ def create(etuid: int = None):
|
||||
)
|
||||
|
||||
|
||||
@bp.route("/assiduite/<int:assiduiteid>/delete", methods=["POST"])
|
||||
@bp.route("/assiduite/delete", methods=["POST"], defaults={"batch": False})
|
||||
@api_web_bp.route("/assiduite/delete", methods=["POST"], defaults={"batch": False})
|
||||
@bp.route(
|
||||
"/assiduite/delete/batch",
|
||||
methods=["POST"],
|
||||
defaults={"batch": True},
|
||||
)
|
||||
@api_web_bp.route(
|
||||
"/assiduite/delete/batch",
|
||||
methods=["POST"],
|
||||
defaults={"batch": True},
|
||||
)
|
||||
@login_required
|
||||
@scodoc
|
||||
@permission_required(Permission.ScoAssiduiteChange)
|
||||
def delete(assiduiteid: int):
|
||||
def delete(batch: bool = False):
|
||||
"""
|
||||
Suppression d'une assiduité à partir de son id
|
||||
"""
|
||||
assiduite: Assiduite = Assiduite.query.filter_by(id=assiduiteid).first_or_404()
|
||||
db.session.delete(assiduite)
|
||||
output = {}
|
||||
if batch:
|
||||
assiduites: list[int] = request.get_json(force=True).get("batch", [])
|
||||
output = {"errors": [], "success": []}
|
||||
|
||||
for ass in assiduites:
|
||||
code, msg = delete_singular(ass, db)
|
||||
if code == 404:
|
||||
output["errors"].append({ass: msg})
|
||||
else:
|
||||
output["success"].append({ass: msg})
|
||||
else:
|
||||
code, msg = delete_singular(
|
||||
request.get_json(force=True).get("assiduite_id", -1), db
|
||||
)
|
||||
output[code] = msg
|
||||
|
||||
db.session.commit()
|
||||
return jsonify({"OK": True})
|
||||
return jsonify(output)
|
||||
|
||||
|
||||
@bp.route("/assiduite/<int:assiduiteid>/edit", methods=["POST"])
|
||||
def delete_singular(assiduite_id: int, db):
|
||||
assiduite: Assiduite = Assiduite.query.filter_by(id=assiduite_id).first()
|
||||
if assiduite is None:
|
||||
return (404, "Assiduite non existante")
|
||||
db.session.delete(assiduite)
|
||||
return (200, "OK")
|
||||
|
||||
|
||||
@bp.route(
|
||||
"/assiduite/<int:assiduiteid>/edit", methods=["POST"], defaults={"batch": False}
|
||||
)
|
||||
@api_web_bp.route(
|
||||
"/assiduite/<int:assiduiteid>/edit", methods=["POST"], defaults={"batch": False}
|
||||
)
|
||||
@bp.route(
|
||||
"/assiduite/<int:assiduiteid>/edit/batch",
|
||||
methods=["POST"],
|
||||
defaults={"batch": True},
|
||||
)
|
||||
@api_web_bp.route(
|
||||
"/assiduite/<int:assiduiteid>/edit/batch",
|
||||
methods=["POST"],
|
||||
defaults={"batch": True},
|
||||
)
|
||||
@login_required
|
||||
@scodoc
|
||||
@permission_required(Permission.ScoAssiduiteChange)
|
||||
def edit(assiduiteid: int):
|
||||
def edit(assiduiteid: int, batch: bool = False):
|
||||
"""
|
||||
Edition d'une assiduité à partir de son id
|
||||
La requête doit avoir un content type "application/json":
|
||||
@ -285,25 +536,3 @@ def change_etat(data: dict, from_int: bool = True):
|
||||
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
|
||||
|
@ -2,13 +2,11 @@
|
||||
"""Gestion de l'assiduité (assiduités + justificatifs)
|
||||
"""
|
||||
from app import db
|
||||
from app.models import ModuleImpl
|
||||
from app.models import ModuleImpl, ModuleImplInscription
|
||||
from app.models.etudiants import Identite
|
||||
from app.models.formsemestre import FormSemestre
|
||||
from app.scodoc.sco_utils import EtatAssiduite
|
||||
from app.scodoc.sco_utils import EtatAssiduite, localize_datetime, verif_interval
|
||||
|
||||
from datetime import datetime
|
||||
from typing import Tuple, List
|
||||
|
||||
|
||||
class Assiduite(db.Model):
|
||||
@ -60,16 +58,22 @@ class Assiduite(db.Model):
|
||||
date_debut: datetime,
|
||||
date_fin: datetime,
|
||||
etat: EtatAssiduite,
|
||||
module: int or None = None or int,
|
||||
module: int or None = None,
|
||||
) -> object or int:
|
||||
"Créer une nouvelle assiduité pour l'étudiant"
|
||||
|
||||
"""Créer une nouvelle assiduité pour l'étudiant
|
||||
Documentation des codes d'erreurs renvoyés:
|
||||
1: Duplication des assiduités (la période rentrée rentre en conflit avec une assiduité enregistrée)
|
||||
2: l'ID du module_impl n'existe pas.
|
||||
"""
|
||||
# Vérification de non duplication des périodes
|
||||
assiduites: List[Assiduite] = etud.assiduites.all()
|
||||
assiduites: list[Assiduite] = etud.assiduites.all()
|
||||
assiduites = [
|
||||
ass
|
||||
for ass in assiduites
|
||||
if verif_interval((date_debut, date_fin), (ass.date_debut, ass.date_fin))
|
||||
if verif_interval(
|
||||
(localize_datetime(date_debut), localize_datetime(date_fin)),
|
||||
(ass.date_debut, ass.date_fin),
|
||||
)
|
||||
]
|
||||
if len(assiduites) != 0:
|
||||
return 1
|
||||
@ -78,8 +82,8 @@ class Assiduite(db.Model):
|
||||
# Vérification de l'existance du module pour l'étudiant
|
||||
if cls.verif_moduleimpl(module, etud):
|
||||
nouv_assiduite = Assiduite(
|
||||
date_debut=date_debut.isoformat(),
|
||||
date_fin=date_fin.isoformat(),
|
||||
date_debut=date_debut,
|
||||
date_fin=date_fin,
|
||||
etat=etat,
|
||||
etudiant=etud,
|
||||
moduleimpl_id=module,
|
||||
@ -93,8 +97,7 @@ class Assiduite(db.Model):
|
||||
etat=etat,
|
||||
etudiant=etud,
|
||||
)
|
||||
db.session.add(nouv_assiduite)
|
||||
db.session.commit()
|
||||
|
||||
return nouv_assiduite
|
||||
|
||||
@staticmethod
|
||||
@ -104,28 +107,22 @@ class Assiduite(db.Model):
|
||||
|
||||
Retourne Vrai si c'est le cas, faux sinon
|
||||
"""
|
||||
# -> get obj module impl -> get obj formsemestres -> query etuds avec etuid -> si vide = Error sinon good
|
||||
|
||||
output = True
|
||||
module: ModuleImpl = ModuleImpl.query.filter_by(
|
||||
moduleimpl_id=moduleimpl_id
|
||||
).first()
|
||||
if module is None:
|
||||
retour = False
|
||||
output = False
|
||||
|
||||
semestre: FormSemestre = FormSemestre.query.filter_by(
|
||||
id=module.formsemestre_id
|
||||
).first()
|
||||
if semestre is None:
|
||||
retour = False
|
||||
if output:
|
||||
search_etudid: int = etud.id if type(etud) == Identite else etud
|
||||
is_module: int = ModuleImplInscription.query.filter_by(
|
||||
etudid=search_etudid, moduleimpl_id=moduleimpl_id
|
||||
).count()
|
||||
|
||||
etudiants: List[Identite] = semestre.etuds.all()
|
||||
output = is_module > 0
|
||||
|
||||
if type(etud) is Identite:
|
||||
retour = etud in etudiants
|
||||
else:
|
||||
retour = etud in [e.id for e in etudiants]
|
||||
|
||||
return retour
|
||||
return output
|
||||
|
||||
|
||||
class Justificatif(db.Model):
|
||||
@ -171,26 +168,3 @@ class Justificatif(db.Model):
|
||||
"fichier": self.fichier,
|
||||
}
|
||||
return data
|
||||
|
||||
|
||||
def verif_interval(periode: Tuple[datetime], interval: Tuple[datetime]) -> bool:
|
||||
"""
|
||||
Vérifie si une période est comprise dans un interval, chevauche l'interval ou comprend l'interval
|
||||
|
||||
Retourne Vrai si c'est le cas, faux sinon
|
||||
"""
|
||||
p_deb, p_fin = periode
|
||||
i_deb, i_fin = interval
|
||||
|
||||
from app.scodoc.intervals import intervalmap
|
||||
|
||||
i = intervalmap()
|
||||
p = intervalmap()
|
||||
i[:] = 0
|
||||
p[:] = 0
|
||||
i[i_deb:i_fin] = 1
|
||||
p[p_deb:p_fin] = 1
|
||||
|
||||
res: int = sum((i[p_deb], i[p_fin], p[i_deb], p[i_fin]))
|
||||
|
||||
return res > 0
|
||||
|
130
app/scodoc/sco_assiduites.py
Normal file
130
app/scodoc/sco_assiduites.py
Normal file
@ -0,0 +1,130 @@
|
||||
from app.models.etudiants import Identite
|
||||
from app.models.formsemestre import FormSemestre
|
||||
from app.models.assiduites import Assiduite
|
||||
import app.scodoc.sco_utils as scu
|
||||
from datetime import datetime, date, time, timedelta
|
||||
|
||||
# TOTALK: Réfléchir sur le fractionnement d'une assiduite prolongée
|
||||
|
||||
|
||||
def get_assiduite_stats(
|
||||
etud: Identite, metric: str = "all", filter: dict[str, object] = {}
|
||||
) -> Assiduite:
|
||||
assiduites: Assiduite = etud.assiduites
|
||||
|
||||
if filter != {}:
|
||||
for key in filter:
|
||||
if key == "etat":
|
||||
assiduites = filter_by_etat(assiduites, filter[key])
|
||||
elif key == "date_fin":
|
||||
assiduites = filter_by_date(assiduites, filter[key], sup=False)
|
||||
elif key == "date_debut":
|
||||
assiduites = filter_by_date(assiduites, filter[key], sup=True)
|
||||
elif key == "moduleimpl_id":
|
||||
assiduites = filter_by_module_impl(assiduites, filter[key])
|
||||
elif key == "formsemestre":
|
||||
assiduites = filter_by_formsemstre(assiduites, filter[key])
|
||||
|
||||
count: dict = get_count(assiduites)
|
||||
|
||||
metrics: list[str] = metric.split(",")
|
||||
|
||||
output: dict = {}
|
||||
|
||||
for key in count:
|
||||
if key in metrics:
|
||||
output[key] = count[key]
|
||||
return output if output != {} else count
|
||||
|
||||
|
||||
def get_count(assiduites: Assiduite) -> dict[str, int or float]:
|
||||
|
||||
output: dict[str, int or float] = {}
|
||||
output["compte"] = assiduites.count()
|
||||
output["heure"] = 0.0
|
||||
output["journee"] = 0
|
||||
output["demi"] = 0
|
||||
|
||||
all_assiduites: list[Assiduite] = assiduites.order_by(Assiduite.date_debut).all()
|
||||
|
||||
current_day: date = None
|
||||
current_time: str = None
|
||||
|
||||
MIDNIGHT: time = time(hour=0)
|
||||
NOON: time = time(hour=12)
|
||||
time_check = lambda d: (MIDNIGHT <= d.time() <= NOON)
|
||||
|
||||
for ass in all_assiduites:
|
||||
delta: timedelta = ass.date_fin - ass.date_debut
|
||||
output["heure"] += delta.total_seconds() / 3600
|
||||
|
||||
ass_time: str = time_check(ass.date_debut)
|
||||
|
||||
if current_day != ass.date_debut.date():
|
||||
current_day = ass.date_debut.date()
|
||||
current_time = ass_time
|
||||
output["demi"] += 1
|
||||
output["journee"] += 1
|
||||
|
||||
if current_time != ass_time:
|
||||
current_time = ass_time
|
||||
output["demi"] += 1
|
||||
|
||||
output["heure"] = round(output["heure"], 2)
|
||||
return output
|
||||
|
||||
|
||||
def filter_by_etat(assiduites: Assiduite, etat: str) -> Assiduite:
|
||||
"""
|
||||
Filtrage d'une collection d'assiduites en fonction de leur état
|
||||
"""
|
||||
etats: list[str] = list(etat.split(","))
|
||||
etats = [scu.ETATS_ASSIDUITE.get(e, "") for e in etats]
|
||||
return assiduites.filter(Assiduite.etat.in_(etats))
|
||||
|
||||
|
||||
def filter_by_date(
|
||||
assiduites: Assiduite, date: datetime, sup: bool = True
|
||||
) -> Assiduite:
|
||||
"""
|
||||
Filtrage d'une collection d'assiduites en fonction d'une date
|
||||
|
||||
Sup == True -> les assiduites doivent débuter après 'date'\n
|
||||
Sup == False -> les assiduites doivent finir avant 'date'
|
||||
"""
|
||||
|
||||
if date.tzinfo is None:
|
||||
first_assiduite: Assiduite = assiduites.first()
|
||||
if first_assiduite is not None:
|
||||
date: datetime = date.replace(tzinfo=first_assiduite.date_debut.tzinfo)
|
||||
|
||||
if sup:
|
||||
return assiduites.filter(Assiduite.date_debut >= date)
|
||||
else:
|
||||
return assiduites.filter(Assiduite.date_fin <= date)
|
||||
|
||||
|
||||
def filter_by_module_impl(
|
||||
assiduites: Assiduite, module_impl_id: int or None
|
||||
) -> Assiduite:
|
||||
"""
|
||||
Filtrage d'une collection d'assiduites en fonction de l'ID du module_impl
|
||||
"""
|
||||
return assiduites.filter(Assiduite.moduleimpl_id == module_impl_id)
|
||||
|
||||
|
||||
def filter_by_formsemstre(assiduites: Assiduite, formsemestre: FormSemestre):
|
||||
"""
|
||||
Filtrage d'une collection d'assiduites en fonction d'un formsemestre
|
||||
"""
|
||||
|
||||
if formsemestre is None:
|
||||
return assiduites.filter(False)
|
||||
|
||||
assiduites = assiduites.filter(
|
||||
Identite.query.filter_by(id=Assiduite.etudid).first()
|
||||
in formsemestre.etuds.all()
|
||||
)
|
||||
|
||||
assiduites = assiduites.filter(Assiduite.date_debut >= formsemestre.date_debut)
|
||||
return assiduites.filter(Assiduite.date_fin <= formsemestre.date_fin)
|
@ -60,6 +60,8 @@ from app import log
|
||||
from app.scodoc.sco_vdi import ApoEtapeVDI
|
||||
from app.scodoc.sco_codes_parcours import NOTES_TOLERANCE, CODES_EXPL
|
||||
from app.scodoc import sco_xml
|
||||
from app.scodoc.intervals import intervalmap
|
||||
|
||||
import sco_version
|
||||
|
||||
# le répertoire static, lié à chaque release pour éviter les problèmes de caches
|
||||
@ -126,6 +128,63 @@ ETAT_JUSTIFICATIF_NAME = {
|
||||
EtatJustificatif.MODIFIE: "modifié",
|
||||
}
|
||||
|
||||
|
||||
def is_iso_formated(date: str, convert=False) -> bool or datetime.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.datetime = dtparser.isoparse(date)
|
||||
return date if convert else True
|
||||
except Exception:
|
||||
return None if convert else False
|
||||
|
||||
|
||||
def localize_datetime(date: datetime.datetime) -> datetime.datetime:
|
||||
|
||||
new_date: datetime.datetime = date
|
||||
if date.tzinfo == None:
|
||||
from app.models.assiduites import Assiduite
|
||||
|
||||
first_assiduite = Assiduite.query.first()
|
||||
if first_assiduite is not None:
|
||||
new_date = date.replace(tzinfo=first_assiduite.date_debut.tzinfo)
|
||||
|
||||
return new_date
|
||||
|
||||
|
||||
def verif_interval(
|
||||
periode: tuple[datetime.datetime], interval: tuple[datetime.datetime]
|
||||
) -> bool:
|
||||
"""
|
||||
Vérifie si une période est comprise dans un interval, chevauche l'interval ou comprend l'interval
|
||||
|
||||
Retourne Vrai si c'est le cas, faux sinon
|
||||
"""
|
||||
p_deb, p_fin = periode
|
||||
i_deb, i_fin = interval
|
||||
|
||||
i = intervalmap()
|
||||
p = intervalmap()
|
||||
i[:] = 0
|
||||
p[:] = 0
|
||||
i[i_deb:i_fin] = 1
|
||||
p[p_deb:p_fin] = 1
|
||||
|
||||
res: int = sum((i[p_deb], i[p_fin], p[i_deb], p[i_fin]))
|
||||
|
||||
return res > 0
|
||||
|
||||
|
||||
# Types de modules
|
||||
class ModuleType(IntEnum):
|
||||
"""Code des types de module."""
|
||||
|
Loading…
Reference in New Issue
Block a user