ScoDoc/app/api/assiduites.py

1056 lines
33 KiB
Python

##############################################################################
# ScoDoc
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
# See LICENSE
##############################################################################
"""ScoDoc 9 API : Assiduités
"""
from datetime import datetime
from flask import g, request
from flask_json import as_json
from flask_login import current_user, login_required
from app import db, log
import app.scodoc.sco_assiduites as scass
import app.scodoc.sco_utils as scu
from app.scodoc import sco_preferences
from app.api import api_bp as bp
from app.api import api_web_bp, get_model_api_object, tools
from app.decorators import permission_required, scodoc
from app.models import (
Assiduite,
FormSemestre,
Identite,
ModuleImpl,
Scolog,
Justificatif,
)
from flask_sqlalchemy.query import Query
from app.models.assiduites import get_assiduites_justif, get_justifs_from_date
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",
"user_id: 1 or null,
"est_just": False or True,
}
"""
return get_model_api_object(Assiduite, assiduite_id, Identite)
@bp.route("/assiduite/<int:assiduite_id>/justificatifs", defaults={"long": False})
@api_web_bp.route(
"/assiduite/<int:assiduite_id>/justificatifs", defaults={"long": False}
)
@bp.route("/assiduite/<int:assiduite_id>/justificatifs/long", defaults={"long": True})
@api_web_bp.route(
"/assiduite/<int:assiduite_id>/justificatifs/long", defaults={"long": True}
)
@scodoc
@permission_required(Permission.ScoView)
@as_json
def assiduite_justificatifs(assiduite_id: int = None, long: bool = False):
"""Retourne la liste des justificatifs qui justifie cette assiduitée
Exemple de résultat:
[
1,
2,
3,
...
]
"""
return get_assiduites_justif(assiduite_id, True)
# etudid
@bp.route("/assiduites/<etudid>/count", defaults={"with_query": False})
@api_web_bp.route("/assiduites/<etudid>/count", defaults={"with_query": False})
@bp.route("/assiduites/<etudid>/count/query", defaults={"with_query": True})
@api_web_bp.route("/assiduites/<etudid>/count/query", defaults={"with_query": True})
@bp.route("/assiduites/etudid/<etudid>/count", defaults={"with_query": False})
@api_web_bp.route("/assiduites/etudid/<etudid>/count", defaults={"with_query": False})
@bp.route("/assiduites/etudid/<etudid>/count/query", defaults={"with_query": True})
@api_web_bp.route(
"/assiduites/etudid/<etudid>/count/query", defaults={"with_query": True}
)
# nip
@bp.route("/assiduites/nip/<nip>/count", defaults={"with_query": False})
@api_web_bp.route("/assiduites/nip/<nip>/count", defaults={"with_query": False})
@bp.route("/assiduites/nip/<nip>/count/query", defaults={"with_query": True})
@api_web_bp.route("/assiduites/nip/<nip>/count/query", defaults={"with_query": True})
# ine
@bp.route("/assiduites/ine/<ine>/count", defaults={"with_query": False})
@api_web_bp.route("/assiduites/ine/<ine>/count", defaults={"with_query": False})
@bp.route("/assiduites/ine/<ine>/count/query", defaults={"with_query": True})
@api_web_bp.route("/assiduites/ine/<ine>/count/query", defaults={"with_query": True})
#
@login_required
@scodoc
@as_json
@permission_required(Permission.ScoView)
def count_assiduites(
etudid: int = None, nip: str = None, ine: str = 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?formsemestre_id=[int]
ex query?formsemestre_id=3
user_id (l'id de l'auteur de l'assiduité)
query?user_id=[int]
ex query?user_id=3
est_just (si l'assiduité est justifié (fait aussi filtre par abs/retard))
query?est_just=[bool]
query?est_just=f
query?est_just=t
"""
# 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)
etud: Identite = tools.get_etud(etudid, nip, ine)
if etud is None:
return json_error(
404,
message="étudiant inconnu",
)
filtered: dict[str, object] = {}
metric: str = "all"
if with_query:
metric, filtered = _count_manager(request)
return scass.get_assiduites_stats(
assiduites=etud.assiduites, metric=metric, filtered=filtered
)
# etudid
@bp.route("/assiduites/<etudid>", defaults={"with_query": False})
@api_web_bp.route("/assiduites/<etudid>", defaults={"with_query": False})
@bp.route("/assiduites/<etudid>/query", defaults={"with_query": True})
@api_web_bp.route("/assiduites/<etudid>/query", defaults={"with_query": True})
@bp.route("/assiduites/etudid/<etudid>", defaults={"with_query": False})
@api_web_bp.route("/assiduites/etudid/<etudid>", defaults={"with_query": False})
@bp.route("/assiduites/etudid/<etudid>/query", defaults={"with_query": True})
@api_web_bp.route("/assiduites/etudid/<etudid>/query", defaults={"with_query": True})
# nip
@bp.route("/assiduites/nip/<nip>", defaults={"with_query": False})
@api_web_bp.route("/assiduites/nip/<nip>", defaults={"with_query": False})
@bp.route("/assiduites/nip/<nip>/query", defaults={"with_query": True})
@api_web_bp.route("/assiduites/nip/<nip>/query", defaults={"with_query": True})
# ine
@bp.route("/assiduites/ine/<ine>", defaults={"with_query": False})
@api_web_bp.route("/assiduites/ine/<ine>", defaults={"with_query": False})
@bp.route("/assiduites/ine/<ine>/query", defaults={"with_query": True})
@api_web_bp.route("/assiduites/ine/<ine>/query", defaults={"with_query": True})
#
@login_required
@scodoc
@as_json
@permission_required(Permission.ScoView)
def assiduites(etudid: int = None, nip=None, ine=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
user_id (l'id de l'auteur de l'assiduité)
query?user_id=[int]
ex query?user_id=3
est_just (si l'assiduité est justifié (fait aussi filtre par abs/retard))
query?est_just=[bool]
query?est_just=f
query?est_just=t
"""
# 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)
etud: Identite = tools.get_etud(etudid, nip, ine)
if etud is None:
return json_error(
404,
message="étudiant inconnu",
)
assiduites_query: 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(format_api=True)
data = _with_justifs(data)
data_set.append(data)
return data_set
@bp.route("/assiduites/group/query", defaults={"with_query": True})
@api_web_bp.route("/assiduites/group/query", defaults={"with_query": True})
@login_required
@scodoc
@as_json
@permission_required(Permission.ScoView)
def assiduites_group(with_query: bool = False):
"""
Retourne toutes les assiduités d'un groupe d'étudiants
chemin : /assiduites/group/query?etudids=1,2,3
Un filtrage peut être donné avec une query
chemin : /assiduites/group/query?etudids=1,2,3
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
user_id (l'id de l'auteur de l'assiduité)
query?user_id=[int]
ex query?user_id=3
est_just (si l'assiduité est justifié (fait aussi filtre par abs/retard))
query?est_just=[bool]
query?est_just=f
query?est_just=t
"""
etuds = request.args.get("etudids", "")
etuds = etuds.split(",")
try:
etuds = [int(etu) for etu in etuds]
except ValueError:
return json_error(404, "Le champs etudids n'est pas correctement formé")
query = Identite.query.filter(Identite.id.in_(etuds))
if g.scodoc_dept:
query = query.filter_by(dept_id=g.scodoc_dept_id)
if len(etuds) != query.count() or len(etuds) == 0:
return json_error(
404,
"Tous les étudiants ne sont pas dans le même département et/ou n'existe pas.",
)
assiduites_query = Assiduite.query.filter(Assiduite.etudid.in_(etuds))
if with_query:
assiduites_query = _filter_manager(request, assiduites_query)
data_set: dict[list[dict]] = {str(key): [] for key in etuds}
for ass in assiduites_query.all():
data = ass.to_dict(format_api=True)
data = _with_justifs(data)
data_set.get(str(data["etudid"])).append(data)
return 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
@as_json
@permission_required(Permission.ScoView)
def assiduites_formsemestre(formsemestre_id: int, with_query: bool = False):
"""Retourne toutes les assiduités du formsemestre"""
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, Assiduite, 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(format_api=True)
data = _with_justifs(data)
data_set.append(data)
return 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
@as_json
@permission_required(Permission.ScoView)
def count_assiduites_formsemestre(
formsemestre_id: int = None, with_query: bool = False
):
"""Comptage des assiduités du formsemestre"""
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, Assiduite, formsemestre
)
metric: str = "all"
filtered: dict = {}
if with_query:
metric, filtered = _count_manager(request)
return scass.get_assiduites_stats(assiduites_query, metric, filtered)
# etudid
@bp.route("/assiduite/<etudid>/create", methods=["POST"])
@api_web_bp.route("/assiduite/<etudid>/create", methods=["POST"])
@bp.route("/assiduite/etudid/<etudid>/create", methods=["POST"])
@api_web_bp.route("/assiduite/etudid/<etudid>/create", methods=["POST"])
# nip
@bp.route("/assiduite/nip/<nip>/create", methods=["POST"])
@api_web_bp.route("/assiduite/nip/<nip>/create", methods=["POST"])
# ine
@bp.route("/assiduite/ine/<ine>/create", methods=["POST"])
@api_web_bp.route("/assiduite/ine/<ine>/create", methods=["POST"])
#
@scodoc
@as_json
@login_required
@permission_required(Permission.AbsChange)
def assiduite_create(etudid: int = None, nip=None, ine=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,
}
...
]
"""
etud: Identite = tools.get_etud(etudid, nip, ine)
if etud is None:
return json_error(
404,
message="étudiant inconnu",
)
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: list = []
success: list = []
for i, data in enumerate(create_list):
code, obj = _create_singular(data, etud)
if code == 404:
errors.append({"indice": i, "message": obj})
else:
success.append({"indice": i, "message": obj})
scass.simple_invalidate_cache(data, etud.id)
db.session.commit()
return {"errors": errors, "success": success}
@bp.route("/assiduites/create", methods=["POST"])
@api_web_bp.route("/assiduites/create", methods=["POST"])
@scodoc
@as_json
@login_required
@permission_required(Permission.AbsChange)
def assiduites_create():
"""
Création d'une assiduité ou plusieurs assiduites
La requête doit avoir un content type "application/json":
[
{
"date_debut": str,
"date_fin": str,
"etat": str,
"etudid":int,
},
{
"date_debut": str,
"date_fin": str,
"etat": str,
"etudid":int,
"moduleimpl_id": int,
"desc":str,
}
...
]
"""
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: list = []
success: list = []
for i, data in enumerate(create_list):
etud: Identite = Identite.query.filter_by(id=data["etudid"]).first()
if etud is None:
errors.append({"indice": i, "message": "Cet étudiant n'existe pas."})
continue
code, obj = _create_singular(data, etud)
if code == 404:
errors.append({"indice": i, "message": obj})
else:
success.append({"indice": i, "message": obj})
scass.simple_invalidate_cache(data)
return {"errors": errors, "success": success}
def _create_singular(
data: dict,
etud: Identite,
) -> tuple[int, object]:
"""TODO: documenter"""
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 not scu.EtatAssiduite.contains(etat):
errors.append("param 'etat': invalide")
etat = scu.EtatAssiduite.get(etat)
# 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 5 : desc
desc: str = data.get("desc", None)
external_data = data.get("external_data", None)
if external_data is not None:
if not isinstance(external_data, dict):
errors.append("param 'external_data' : n'est pas un objet JSON")
# cas 4 : moduleimpl_id
moduleimpl_id = data.get("moduleimpl_id", False)
moduleimpl: ModuleImpl = None
if moduleimpl_id not in [False, None, "", "-1"]:
if moduleimpl_id != "autre":
try:
moduleimpl = ModuleImpl.query.filter_by(id=int(moduleimpl_id)).first()
except ValueError:
moduleimpl = None
if moduleimpl is None:
errors.append("param 'moduleimpl_id': invalide")
else:
moduleimpl_id = None
external_data = external_data if external_data is not None else {}
external_data["module"] = "Autre"
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,
user_id=current_user.id,
external_data=external_data,
)
db.session.add(nouv_assiduite)
db.session.commit()
return (200, {"assiduite_id": nouv_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
@as_json
@permission_required(Permission.AbsChange)
def assiduite_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"].append({"indice": i, "message": msg})
else:
output["success"].append({"indice": i, "message": "OK"})
db.session.commit()
return 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")
ass_dict = assiduite_unique.to_dict()
log(f"delete_assiduite: {assiduite_unique.etudiant.id} {assiduite_unique}")
Scolog.logdb(
method="delete_assiduite",
etudid=assiduite_unique.etudiant.id,
msg=f"assiduité: {assiduite_unique}",
)
database.session.delete(assiduite_unique)
scass.simple_invalidate_cache(ass_dict)
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
@as_json
@permission_required(Permission.AbsChange)
def assiduite_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
"est_just"?: bool
}
"""
assiduite_unique: Assiduite = Assiduite.query.filter_by(
id=assiduite_id
).first_or_404()
errors: list[str] = []
data = request.get_json(force=True)
code, obj = _edit_singular(assiduite_unique, data)
if code == 404:
return json_error(404, obj)
log(f"assiduite_edit: {assiduite_unique.etudiant.id} {assiduite_unique}")
Scolog.logdb(
"assiduite_edit",
assiduite_unique.etudiant.id,
msg=f"assiduite: modif {assiduite_unique}",
)
db.session.commit()
scass.simple_invalidate_cache(assiduite_unique.to_dict())
return {"OK": True}
@bp.route("/assiduites/edit", methods=["POST"])
@api_web_bp.route("/assiduites/edit", methods=["POST"])
@login_required
@scodoc
@as_json
@permission_required(Permission.AbsChange)
def assiduites_edit():
"""
Edition de plusieurs assiduités
La requête doit avoir un content type "application/json":
[
{
"assiduite_id" : int,
"etat"?: str,
"moduleimpl_id"?: int
"desc"?: str
"est_just"?: bool
}
]
"""
edit_list: list[object] = request.get_json(force=True)
if not isinstance(edit_list, list):
return json_error(404, "Le contenu envoyé n'est pas une liste")
errors: list[dict] = []
success: list[dict] = []
for i, data in enumerate(edit_list):
assi: Identite = Assiduite.query.filter_by(id=data["assiduite_id"]).first()
if assi is None:
errors.append(
{
"indice": i,
"message": f"assiduité {data['assiduite_id']} n'existe pas.",
}
)
continue
code, obj = _edit_singular(assi, data)
obj_retour = {
"indice": i,
"message": obj,
}
if code == 404:
errors.append(obj_retour)
else:
success.append(obj_retour)
db.session.commit()
return {"errors": errors, "success": success}
def _edit_singular(assiduite_unique, data):
errors: list[str] = []
# Vérifications de data
# Cas 1 : Etat
if data.get("etat") is not None:
etat = scu.EtatAssiduite.get(data.get("etat"))
if etat is None:
errors.append("param 'etat': invalide")
else:
assiduite_unique.etat = etat
external_data = data.get("external_data")
if external_data is not None:
if not isinstance(external_data, dict):
errors.append("param 'external_data' : n'est pas un objet JSON")
else:
assiduite_unique.external_data = external_data
# Cas 2 : Moduleimpl_id
moduleimpl_id = data.get("moduleimpl_id", False)
moduleimpl: ModuleImpl = None
if moduleimpl_id is not False:
if moduleimpl_id not in [None, "", "-1"]:
if moduleimpl_id == "autre":
assiduite_unique.moduleimpl_id = None
external_data = (
external_data
if external_data is not None and isinstance(external_data, dict)
else assiduite_unique.external_data
)
external_data = external_data if external_data is not None else {}
external_data["module"] = "Autre"
assiduite_unique.external_data = external_data
else:
try:
moduleimpl = ModuleImpl.query.filter_by(
id=int(moduleimpl_id)
).first()
except ValueError:
moduleimpl = None
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 = None
# Cas 3 : desc
desc = data.get("desc", False)
if desc is not False:
assiduite_unique.description = desc
# Cas 4 : est_just
if assiduite_unique.etat == scu.EtatAssiduite.PRESENT:
assiduite_unique.est_just = False
else:
assiduite_unique.est_just = (
len(
get_justifs_from_date(
assiduite_unique.etudiant.id,
assiduite_unique.date_debut,
assiduite_unique.date_fin,
valid=True,
)
)
> 0
)
if errors:
err: str = ", ".join(errors)
return (404, err)
log(f"_edit_singular: {assiduite_unique.etudiant.id} {assiduite_unique}")
Scolog.logdb(
"assiduite_edit",
assiduite_unique.etudiant.id,
msg=f"assiduite: modif {assiduite_unique}",
)
db.session.add(assiduite_unique)
scass.simple_invalidate_cache(assiduite_unique.to_dict())
return (200, "OK")
# -- Utils --
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", "").replace(" ", "+")
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", "").replace(" ", "+")
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")
# cas 7 : est_just
est_just: str = requested.args.get("est_just")
if est_just is not None:
trues: tuple[str] = ("v", "t", "vrai", "true", "1")
falses: tuple[str] = ("f", "faux", "false", "0")
if est_just.lower() in trues:
filtered["est_just"] = True
elif est_just.lower() in falses:
filtered["est_just"] = False
# cas 8 : user_id
user_id = requested.args.get("user_id", False)
if user_id is not False:
filtered["user_id"] = user_id
# cas 9 : split
split = requested.args.get("split", False)
if split is not False:
filtered["split"] = True
return (metric, filtered)
def _filter_manager(requested, assiduites_query: Query) -> 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_assiduites_by_etat(assiduites_query, etat)
# cas 2 : date de début
deb = requested.args.get("date_debut", "").replace(" ", "+")
deb: datetime = scu.is_iso_formated(deb, True)
# cas 3 : date de fin
fin = requested.args.get("date_fin", "").replace(" ", "+")
fin = scu.is_iso_formated(fin, True)
if (deb, fin) != (None, None):
assiduites_query: Query = scass.filter_by_date(
assiduites_query, Assiduite, deb, 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:
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, Assiduite, formsemestre
)
# cas 6 : est_just
est_just: str = requested.args.get("est_just")
if est_just is not None:
trues: tuple[str] = ("v", "t", "vrai", "true", "1")
falses: tuple[str] = ("f", "faux", "false", "0")
if est_just.lower() in trues:
assiduites_query: Query = scass.filter_assiduites_by_est_just(
assiduites_query, True
)
elif est_just.lower() in falses:
assiduites_query: Query = scass.filter_assiduites_by_est_just(
assiduites_query, False
)
# cas 8 : user_id
user_id = requested.args.get("user_id", False)
if user_id is not False:
assiduites_query: Query = scass.filter_by_user_id(assiduites_query, user_id)
order = requested.args.get("order", None)
if order is not None:
assiduites_query: Query = assiduites_query.order_by(Assiduite.date_debut.desc())
courant = requested.args.get("courant", None)
if courant is not None:
annee: int = scu.annee_scolaire()
assiduites_query: Query = assiduites_query.filter(
Assiduite.date_debut >= scu.date_debut_anne_scolaire(annee),
Assiduite.date_fin <= scu.date_fin_anne_scolaire(annee),
)
return assiduites_query
def _with_justifs(assi):
if request.args.get("with_justifs") is None:
return assi
assi["justificatifs"] = get_assiduites_justif(assi["assiduite_id"], True)
return assi