forked from ScoDoc/ScoDoc
Merge branch 'main96' into liste_assi
This commit is contained in:
commit
bb4a427207
@ -163,23 +163,22 @@ def count_assiduites(
|
||||
|
||||
|
||||
"""
|
||||
# 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)
|
||||
|
||||
# Récupération de l'étudiant
|
||||
etud: Identite = tools.get_etud(etudid, nip, ine)
|
||||
|
||||
# Vérification que l'étudiant existe
|
||||
if etud is None:
|
||||
return json_error(
|
||||
404,
|
||||
message="étudiant inconnu",
|
||||
)
|
||||
|
||||
# Les filtres qui seront appliqués au comptage (type, date, etudid...)
|
||||
filtered: dict[str, object] = {}
|
||||
# la métrique du comptage (all, demi, heure, journee)
|
||||
metric: str = "all"
|
||||
|
||||
# Si la requête a des paramètres
|
||||
if with_query:
|
||||
metric, filtered = _count_manager(request)
|
||||
|
||||
@ -254,11 +253,7 @@ def assiduites(etudid: int = None, nip=None, ine=None, with_query: bool = False)
|
||||
|
||||
"""
|
||||
|
||||
# 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)
|
||||
# Récupération de l'étudiant
|
||||
etud: Identite = tools.get_etud(etudid, nip, ine)
|
||||
|
||||
if etud is None:
|
||||
@ -266,15 +261,23 @@ def assiduites(etudid: int = None, nip=None, ine=None, with_query: bool = False)
|
||||
404,
|
||||
message="étudiant inconnu",
|
||||
)
|
||||
# Récupération des assiduités de l'étudiant
|
||||
assiduites_query: Query = etud.assiduites
|
||||
|
||||
# Filtrage des assiduités en fonction des paramètres de la requête
|
||||
if with_query:
|
||||
assiduites_query = _filter_manager(request, assiduites_query)
|
||||
|
||||
# Préparation de la réponse json
|
||||
|
||||
data_set: list[dict] = []
|
||||
|
||||
for ass in assiduites_query.all():
|
||||
# conversion Assiduite -> Dict
|
||||
data = ass.to_dict(format_api=True)
|
||||
# Ajout des justificatifs (ou non dépendamment de la requête)
|
||||
data = _with_justifs(data)
|
||||
# Ajout de l'assiduité dans la liste de retour
|
||||
data_set.append(data)
|
||||
|
||||
return data_set
|
||||
@ -326,6 +329,7 @@ def assiduites_group(with_query: bool = False):
|
||||
|
||||
"""
|
||||
|
||||
# Récupération des étudiants dans la requête
|
||||
etuds = request.args.get("etudids", "")
|
||||
etuds = etuds.split(",")
|
||||
try:
|
||||
@ -333,6 +337,7 @@ def assiduites_group(with_query: bool = False):
|
||||
except ValueError:
|
||||
return json_error(404, "Le champs etudids n'est pas correctement formé")
|
||||
|
||||
# Vérification que tous les étudiants sont du même département
|
||||
query = Identite.query.filter(Identite.id.in_(etuds))
|
||||
if g.scodoc_dept:
|
||||
query = query.filter_by(dept_id=g.scodoc_dept_id)
|
||||
@ -342,15 +347,21 @@ def assiduites_group(with_query: bool = False):
|
||||
404,
|
||||
"Tous les étudiants ne sont pas dans le même département et/ou n'existe pas.",
|
||||
)
|
||||
|
||||
# Récupération de toutes les assiduités liés aux étudiants
|
||||
assiduites_query = Assiduite.query.filter(Assiduite.etudid.in_(etuds))
|
||||
|
||||
# Filtrage des assiduités en fonction des filtres passés dans la requête
|
||||
if with_query:
|
||||
assiduites_query = _filter_manager(request, assiduites_query)
|
||||
|
||||
# Préparation de retour json
|
||||
# Dict représentant chaque étudiant avec sa liste d'assiduité
|
||||
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)
|
||||
# Ajout de l'assiduité dans la liste du bon étudiant
|
||||
data_set.get(str(data["etudid"])).append(data)
|
||||
return data_set
|
||||
|
||||
@ -375,20 +386,23 @@ def assiduites_group(with_query: bool = False):
|
||||
@permission_required(Permission.ScoView)
|
||||
def assiduites_formsemestre(formsemestre_id: int, with_query: bool = False):
|
||||
"""Retourne toutes les assiduités du formsemestre"""
|
||||
|
||||
# Récupération du formsemestre à partir du formsemestre_id
|
||||
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")
|
||||
|
||||
# Récupération des assiduités du formsemestre
|
||||
assiduites_query = scass.filter_by_formsemestre(
|
||||
Assiduite.query, Assiduite, formsemestre
|
||||
)
|
||||
|
||||
# Filtrage en fonction des paramètres de la requête
|
||||
if with_query:
|
||||
assiduites_query = _filter_manager(request, assiduites_query)
|
||||
|
||||
# Préparation du retour JSON
|
||||
data_set: list[dict] = []
|
||||
for ass in assiduites_query.all():
|
||||
data = ass.to_dict(format_api=True)
|
||||
@ -422,21 +436,28 @@ def count_assiduites_formsemestre(
|
||||
formsemestre_id: int = None, with_query: bool = False
|
||||
):
|
||||
"""Comptage des assiduités du formsemestre"""
|
||||
|
||||
# Récupération du formsemestre à partir du formsemestre_id
|
||||
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")
|
||||
|
||||
# Récupération des étudiants du formsemestre
|
||||
etuds = formsemestre.etuds.all()
|
||||
etuds_id = [etud.id for etud in etuds]
|
||||
|
||||
# Récupération des assiduités des étudiants du semestre
|
||||
assiduites_query = Assiduite.query.filter(Assiduite.etudid.in_(etuds_id))
|
||||
# Filtrage des assiduités en fonction des dates du semestre
|
||||
assiduites_query = scass.filter_by_formsemestre(
|
||||
assiduites_query, Assiduite, formsemestre
|
||||
)
|
||||
|
||||
# Gestion de la métrique de comptage (all,demi,heure,journee)
|
||||
metric: str = "all"
|
||||
# Gestion du filtre (en fonction des paramètres de la requête)
|
||||
filtered: dict = {}
|
||||
if with_query:
|
||||
metric, filtered = _count_manager(request)
|
||||
@ -481,23 +502,36 @@ def assiduite_create(etudid: int = None, nip=None, ine=None):
|
||||
]
|
||||
|
||||
"""
|
||||
# Récupération de l'étudiant
|
||||
etud: Identite = tools.get_etud(etudid, nip, ine)
|
||||
if etud is None:
|
||||
return json_error(
|
||||
404,
|
||||
message="étudiant inconnu",
|
||||
)
|
||||
# Mise à jour du "g.scodoc_dept" si route sans dept
|
||||
if g.scodoc_dept is None and etud.dept_id is not None:
|
||||
# route sans département
|
||||
set_sco_dept(etud.departement.acronym)
|
||||
|
||||
# Récupération de la liste des assiduités à créer
|
||||
create_list: list[object] = request.get_json(force=True)
|
||||
|
||||
# Vérification que c'est bien une liste
|
||||
if not isinstance(create_list, list):
|
||||
return json_error(404, "Le contenu envoyé n'est pas une liste")
|
||||
|
||||
errors: list = []
|
||||
success: list = []
|
||||
# Préparation du retour
|
||||
|
||||
errors: list[dict[str, object]] = []
|
||||
success: list[dict[str, object]] = []
|
||||
|
||||
# Pour chaque objet de la liste,
|
||||
# on récupère son indice et l'objet
|
||||
for i, data in enumerate(create_list):
|
||||
# On créé l'assiduité
|
||||
# 200 + obj si réussi
|
||||
# 404 + message d'erreur si non réussi
|
||||
code, obj = _create_one(data, etud)
|
||||
if code == 404:
|
||||
errors.append({"indice": i, "message": obj})
|
||||
@ -570,53 +604,77 @@ def _create_one(
|
||||
data: dict,
|
||||
etud: Identite,
|
||||
) -> tuple[int, object]:
|
||||
"""TODO @iziram: documenter"""
|
||||
"""
|
||||
_create_one Création d'une assiduité à partir d'une représentation JSON
|
||||
|
||||
Cette fonction vérifie la représentation JSON
|
||||
|
||||
Puis crée l'assiduité si la représentation est valide.
|
||||
|
||||
Args:
|
||||
data (dict): représentation json d'une assiduité
|
||||
etud (Identite): l'étudiant concerné par l'assiduité
|
||||
|
||||
Returns:
|
||||
tuple[int, object]: code, objet
|
||||
code : 200 si réussi 404 sinon
|
||||
objet : dict{assiduite_id:?} si réussi str"message d'erreur" sinon
|
||||
"""
|
||||
|
||||
errors: list[str] = []
|
||||
|
||||
# -- vérifications de l'objet json --
|
||||
# cas 1 : ETAT
|
||||
etat = data.get("etat", None)
|
||||
etat: str = 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)
|
||||
etat: scu.EtatAssiduite = scu.EtatAssiduite.get(etat)
|
||||
|
||||
# cas 2 : date_debut
|
||||
date_debut = data.get("date_debut", None)
|
||||
date_debut: str = 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)
|
||||
# Conversion de la chaine de caractère en datetime (tz ou non)
|
||||
deb: datetime = scu.is_iso_formated(date_debut, convert=True)
|
||||
# si chaine invalide
|
||||
if deb is None:
|
||||
errors.append("param 'date_debut': format invalide")
|
||||
# Si datetime sans timezone
|
||||
elif deb.tzinfo is None:
|
||||
deb = scu.localize_datetime(deb)
|
||||
# Mise à jour de la timezone avec celle du serveur
|
||||
deb: datetime = scu.localize_datetime(deb)
|
||||
|
||||
# cas 3 : date_fin
|
||||
date_fin = data.get("date_fin", None)
|
||||
# cas 3 : date_fin (Même fonctionnement ^ )
|
||||
date_fin: str = 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)
|
||||
fin: datetime = scu.is_iso_formated(date_fin, convert=True)
|
||||
if fin is None:
|
||||
errors.append("param 'date_fin': format invalide")
|
||||
elif fin.tzinfo is None:
|
||||
fin = scu.localize_datetime(fin)
|
||||
fin: datetime = scu.localize_datetime(fin)
|
||||
|
||||
# cas 5 : desc
|
||||
# cas 4 : desc
|
||||
desc: str = data.get("desc", None)
|
||||
|
||||
external_data = data.get("external_data", None)
|
||||
# cas 5 : external data
|
||||
external_data: dict = 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
|
||||
# cas 6 : moduleimpl_id
|
||||
|
||||
# On récupère le moduleimpl
|
||||
moduleimpl_id = data.get("moduleimpl_id", False)
|
||||
moduleimpl: ModuleImpl = None
|
||||
|
||||
# On vérifie si le moduleimpl existe (uniquement s'il a été renseigné)
|
||||
if moduleimpl_id not in [False, None, "", "-1"]:
|
||||
# Si le moduleimpl n'est pas "autre" alors on vérifie si l'id est valide
|
||||
if moduleimpl_id != "autre":
|
||||
try:
|
||||
moduleimpl = ModuleImpl.query.filter_by(id=int(moduleimpl_id)).first()
|
||||
@ -625,16 +683,23 @@ def _create_one(
|
||||
if moduleimpl is None:
|
||||
errors.append("param 'moduleimpl_id': invalide")
|
||||
else:
|
||||
# Sinon on met à none le moduleimpl
|
||||
# et on ajoute dans external data
|
||||
# le module "autre"
|
||||
moduleimpl_id = None
|
||||
external_data = external_data if external_data is not None else {}
|
||||
external_data: dict = external_data if external_data is not None else {}
|
||||
external_data["module"] = "Autre"
|
||||
|
||||
# Si il y a des erreurs alors on ne crée pas l'assiduité et on renvoie les erreurs
|
||||
if errors:
|
||||
# Construit une chaine de caractère avec les erreurs séparées par `,`
|
||||
err: str = ", ".join(errors)
|
||||
# 404 représente le code d'erreur et err la chaine nouvellement créée
|
||||
return (404, err)
|
||||
|
||||
# TOUT EST OK
|
||||
# SI TOUT EST OK
|
||||
try:
|
||||
# On essaye de créer l'assiduité
|
||||
nouv_assiduite: Assiduite = Assiduite.create_assiduite(
|
||||
date_debut=deb,
|
||||
date_fin=fin,
|
||||
@ -647,12 +712,23 @@ def _create_one(
|
||||
notify_mail=True,
|
||||
)
|
||||
|
||||
# create_assiduite générera des ScoValueError si jamais il y a un autre problème
|
||||
# - Etudiant non inscrit dans le module
|
||||
# - module obligatoire
|
||||
# - Assiduité conflictuelles
|
||||
|
||||
# Si tout s'est bien passé on ajoute l'assiduité à la session
|
||||
# et on retourne un code 200 avec un objet possèdant l'assiduite_id
|
||||
db.session.add(nouv_assiduite)
|
||||
db.session.commit()
|
||||
|
||||
return (200, {"assiduite_id": nouv_assiduite.id})
|
||||
except ScoValueError as excp:
|
||||
# ici on utilise pas json_error car on doit renvoyer status, message
|
||||
# Ici json_error ne peut être utilisé car il terminerai le processus de création
|
||||
# Cela voudrait dire qu'une seule erreur dans une assiduité imposerait de
|
||||
# tout refaire à partir de l'erreur.
|
||||
|
||||
# renvoit un code 404 et le message d'erreur de la ScoValueError
|
||||
return 404, excp.args[0]
|
||||
|
||||
|
||||
@ -675,14 +751,20 @@ def assiduite_delete():
|
||||
|
||||
|
||||
"""
|
||||
# Récupération des ids envoyés dans la liste
|
||||
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")
|
||||
|
||||
# Préparation du retour json
|
||||
output = {"errors": [], "success": []}
|
||||
|
||||
for i, ass in enumerate(assiduites_list):
|
||||
code, msg = _delete_singular(ass, db)
|
||||
# Pour chaque assiduite_id on essaye de supprimer l'assiduité
|
||||
for i, assiduite_id in enumerate(assiduites_list):
|
||||
# De la même façon que "_create_one"
|
||||
# Ici le code est soit 200 si réussi ou 404 si raté
|
||||
# Le message est le message d'erreur si erreur
|
||||
code, msg = _delete_one(assiduite_id)
|
||||
if code == 404:
|
||||
output["errors"].append({"indice": i, "message": msg})
|
||||
else:
|
||||
@ -692,24 +774,43 @@ def assiduite_delete():
|
||||
return output
|
||||
|
||||
|
||||
def _delete_singular(assiduite_id: int, database) -> tuple[int, str]:
|
||||
"""@iziram PLEASE COMMENT THIS F*CKING CODE"""
|
||||
def _delete_one(assiduite_id: int) -> tuple[int, str]:
|
||||
"""
|
||||
_delete_singular Supprime une assiduité à partir de son id
|
||||
|
||||
Args:
|
||||
assiduite_id (int): l'identifiant de l'assiduité
|
||||
Returns:
|
||||
tuple[int, str]: code, message
|
||||
code : 200 si réussi, 404 sinon
|
||||
message : OK si réussi, le message d'erreur sinon
|
||||
"""
|
||||
assiduite_unique: Assiduite = Assiduite.query.filter_by(id=assiduite_id).first()
|
||||
if assiduite_unique is None:
|
||||
# on ne peut pas utiliser json_error ici car on est déclaré (int, str)
|
||||
# Ici json_error ne peut être utilisé car il terminerai le processus de création
|
||||
# Cela voudrait dire qu'une seule erreur d'id imposerait de
|
||||
# tout refaire à partir de l'erreur.
|
||||
return 404, "Assiduite non existante"
|
||||
|
||||
# Mise à jour du g.scodoc_dept si la route est sans département
|
||||
if g.scodoc_dept is None and assiduite_unique.etudiant.dept_id is not None:
|
||||
# route sans département
|
||||
set_sco_dept(assiduite_unique.etudiant.departement.acronym)
|
||||
ass_dict = assiduite_unique.to_dict()
|
||||
|
||||
# Récupération de la version dict de l'assiduité
|
||||
# Pour invalider le cache
|
||||
assi_dict = assiduite_unique.to_dict()
|
||||
|
||||
# Suppression de l'assiduité et LOG
|
||||
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)
|
||||
db.session.delete(assiduite_unique)
|
||||
# Invalidation du cache
|
||||
scass.simple_invalidate_cache(assi_dict)
|
||||
return 200, "OK"
|
||||
|
||||
|
||||
@ -730,17 +831,25 @@ def assiduite_edit(assiduite_id: int):
|
||||
"est_just"?: bool
|
||||
}
|
||||
"""
|
||||
|
||||
# Récupération de l'assiduité à modifier
|
||||
assiduite_unique: Assiduite = Assiduite.query.filter_by(
|
||||
id=assiduite_id
|
||||
).first_or_404()
|
||||
errors: list[str] = []
|
||||
# Récupération des valeurs à modifier
|
||||
data = request.get_json(force=True)
|
||||
|
||||
code, obj = _edit_singular(assiduite_unique, data)
|
||||
# Préparation du retour
|
||||
errors: list[str] = []
|
||||
|
||||
# Code 200 si modification réussie
|
||||
# Code 404 si raté + message d'erreur
|
||||
code, obj = _edit_one(assiduite_unique, data)
|
||||
|
||||
if code == 404:
|
||||
return json_error(404, obj)
|
||||
|
||||
# Mise à jour de l'assiduité et LOG
|
||||
log(f"assiduite_edit: {assiduite_unique.etudiant.id} {assiduite_unique}")
|
||||
Scolog.logdb(
|
||||
"assiduite_edit",
|
||||
@ -791,7 +900,7 @@ def assiduites_edit():
|
||||
)
|
||||
continue
|
||||
|
||||
code, obj = _edit_singular(assi, data)
|
||||
code, obj = _edit_one(assi, data)
|
||||
obj_retour = {
|
||||
"indice": i,
|
||||
"message": obj,
|
||||
@ -806,46 +915,69 @@ def assiduites_edit():
|
||||
return {"errors": errors, "success": success}
|
||||
|
||||
|
||||
def _edit_singular(assiduite_unique, data):
|
||||
def _edit_one(assiduite_unique: Assiduite, data: dict) -> tuple[int, str]:
|
||||
"""
|
||||
_edit_singular Modifie une assiduité à partir de données JSON
|
||||
|
||||
Args:
|
||||
assiduite_unique (Assiduite): l'assiduité à modifier
|
||||
data (dict): les nouvelles données
|
||||
|
||||
Returns:
|
||||
tuple[int,str]: code, message
|
||||
code : 200 si réussi, 404 sinon
|
||||
message : OK si réussi, message d'erreur sinon
|
||||
"""
|
||||
|
||||
# Mise à jour du g.scodoc_dept en cas de route sans département
|
||||
if g.scodoc_dept is None and assiduite_unique.etudiant.dept_id is not None:
|
||||
# route sans département
|
||||
set_sco_dept(assiduite_unique.etudiant.departement.acronym)
|
||||
|
||||
errors: list[str] = []
|
||||
|
||||
# Vérifications de data
|
||||
|
||||
# Cas 1 : Etat
|
||||
if data.get("etat") is not None:
|
||||
etat = scu.EtatAssiduite.get(data.get("etat"))
|
||||
etat: scu.EtatAssiduite = scu.EtatAssiduite.get(data.get("etat"))
|
||||
if etat is None:
|
||||
errors.append("param 'etat': invalide")
|
||||
else:
|
||||
# Mise à jour de l'état
|
||||
assiduite_unique.etat = etat
|
||||
|
||||
external_data = data.get("external_data")
|
||||
# Cas 2 : external_data
|
||||
external_data: dict = 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:
|
||||
# Mise à jour de l'external data
|
||||
assiduite_unique.external_data = external_data
|
||||
|
||||
# Cas 2 : Moduleimpl_id
|
||||
# Cas 3 : Moduleimpl_id
|
||||
moduleimpl_id = data.get("moduleimpl_id", False)
|
||||
moduleimpl: ModuleImpl = None
|
||||
|
||||
# False si on modifie pas le moduleimpl
|
||||
if moduleimpl_id is not False:
|
||||
# Si le module n'est pas nul
|
||||
if moduleimpl_id not in [None, "", "-1"]:
|
||||
# Gestion du module Autre
|
||||
if moduleimpl_id == "autre":
|
||||
# module autre = moduleimpl_id:None + external_data["module"]:"Autre"
|
||||
assiduite_unique.moduleimpl_id = None
|
||||
external_data = (
|
||||
external_data: dict = (
|
||||
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: dict = external_data if external_data is not None else {}
|
||||
external_data["module"] = "Autre"
|
||||
assiduite_unique.external_data = external_data
|
||||
else:
|
||||
# Vérification de l'id et récupération de l'objet ModuleImpl
|
||||
try:
|
||||
moduleimpl = ModuleImpl.query.filter_by(
|
||||
id=int(moduleimpl_id)
|
||||
@ -861,8 +993,11 @@ def _edit_singular(assiduite_unique, data):
|
||||
):
|
||||
errors.append("param 'moduleimpl_id': etud non inscrit")
|
||||
else:
|
||||
# Mise à jour du moduleimpl
|
||||
assiduite_unique.moduleimpl_id = moduleimpl_id
|
||||
else:
|
||||
# Vérification du force module en cas de modification du moduleimpl en moduleimpl nul
|
||||
# Récupération du formsemestre lié à l'assiduité
|
||||
formsemestre: FormSemestre = get_formsemestre_from_data(
|
||||
assiduite_unique.to_dict()
|
||||
)
|
||||
@ -873,17 +1008,23 @@ def _edit_singular(assiduite_unique, data):
|
||||
else:
|
||||
force = scu.is_assiduites_module_forced(dept_id=etud.dept_id)
|
||||
|
||||
if force:
|
||||
external_data = (
|
||||
external_data
|
||||
if external_data is not None and isinstance(external_data, dict)
|
||||
else assiduite_unique.external_data
|
||||
)
|
||||
|
||||
if force and not external_data.get("module", False):
|
||||
errors.append(
|
||||
"param 'moduleimpl_id' : le moduleimpl_id ne peut pas être nul"
|
||||
)
|
||||
|
||||
# Cas 3 : desc
|
||||
desc = data.get("desc", False)
|
||||
# Cas 4 : desc
|
||||
desc: str = data.get("desc", False)
|
||||
if desc is not False:
|
||||
assiduite_unique.description = desc
|
||||
|
||||
# Cas 4 : est_just
|
||||
# Cas 5 : est_just
|
||||
if assiduite_unique.etat == scu.EtatAssiduite.PRESENT:
|
||||
assiduite_unique.est_just = False
|
||||
else:
|
||||
@ -900,9 +1041,11 @@ def _edit_singular(assiduite_unique, data):
|
||||
)
|
||||
|
||||
if errors:
|
||||
# Retour des erreurs en une seule chaîne séparée par des `,`
|
||||
err: str = ", ".join(errors)
|
||||
return (404, err)
|
||||
|
||||
# Mise à jour de l'assiduité et LOG
|
||||
log(f"_edit_singular: {assiduite_unique.etudiant.id} {assiduite_unique}")
|
||||
Scolog.logdb(
|
||||
"assiduite_edit",
|
||||
@ -997,21 +1140,34 @@ def _count_manager(requested) -> tuple[str, dict]:
|
||||
|
||||
def _filter_manager(requested, assiduites_query: Query) -> Query:
|
||||
"""
|
||||
Retourne les assiduites entrées filtrées en fonction de la request
|
||||
_filter_manager Retourne les assiduites entrées filtrées en fonction de la request
|
||||
|
||||
Args:
|
||||
requested (request): La requête http
|
||||
assiduites_query (Query): la query d'assiduités à filtrer
|
||||
|
||||
Returns:
|
||||
Query: La query filtrée
|
||||
"""
|
||||
# cas 1 : etat assiduite
|
||||
etat = requested.args.get("etat")
|
||||
etat: str = 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)
|
||||
deb: str = requested.args.get("date_debut", "").replace(" ", "+")
|
||||
deb: datetime = scu.is_iso_formated(
|
||||
deb, True
|
||||
) # transformation de la chaine en datetime
|
||||
|
||||
# cas 3 : date de fin
|
||||
fin = requested.args.get("date_fin", "").replace(" ", "+")
|
||||
fin = scu.is_iso_formated(fin, True)
|
||||
fin: str = requested.args.get("date_fin", "").replace(" ", "+")
|
||||
fin: datetime = scu.is_iso_formated(
|
||||
fin, True
|
||||
) # transformation de la chaine en datetime
|
||||
|
||||
# Pour filtrer les dates il faut forcement avoir les deux bornes
|
||||
# [date_debut : date_fin]
|
||||
if (deb, fin) != (None, None):
|
||||
assiduites_query: Query = scass.filter_by_date(
|
||||
assiduites_query, Assiduite, deb, fin
|
||||
@ -1065,10 +1221,12 @@ def _filter_manager(requested, assiduites_query: Query) -> Query:
|
||||
if user_id is not False:
|
||||
assiduites_query: Query = scass.filter_by_user_id(assiduites_query, user_id)
|
||||
|
||||
# cas 9 : order (renvoie la query ordonnée en "date début Décroissante")
|
||||
order = requested.args.get("order", None)
|
||||
if order is not None:
|
||||
assiduites_query: Query = assiduites_query.order_by(Assiduite.date_debut.desc())
|
||||
|
||||
# cas 10 : courant (Ne renvoie que les assiduités de l'année courante)
|
||||
courant = requested.args.get("courant", None)
|
||||
if courant is not None:
|
||||
annee: int = scu.annee_scolaire()
|
||||
@ -1081,7 +1239,17 @@ def _filter_manager(requested, assiduites_query: Query) -> Query:
|
||||
return assiduites_query
|
||||
|
||||
|
||||
def _with_justifs(assi):
|
||||
def _with_justifs(assi: dict):
|
||||
"""
|
||||
_with_justifs ajoute la liste des justificatifs à l'assiduité
|
||||
|
||||
Condition : `with_justifs` doit se trouver dans les paramètres de la requête
|
||||
Args:
|
||||
assi (dict): un dictionnaire représentant une assiduité
|
||||
|
||||
Returns:
|
||||
dict: l'assiduité avec les justificatifs ajoutés
|
||||
"""
|
||||
if request.args.get("with_justifs") is None:
|
||||
return assi
|
||||
assi["justificatifs"] = get_assiduites_justif(assi["assiduite_id"], True)
|
||||
|
@ -3,7 +3,7 @@
|
||||
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
||||
# See LICENSE
|
||||
##############################################################################
|
||||
"""ScoDoc 9 API : Assiduités
|
||||
"""ScoDoc 9 API : Justificatifs
|
||||
"""
|
||||
from datetime import datetime
|
||||
|
||||
@ -28,6 +28,7 @@ from app.models import (
|
||||
)
|
||||
from app.models.assiduites import (
|
||||
compute_assiduites_justified,
|
||||
get_formsemestre_from_data,
|
||||
)
|
||||
from app.scodoc.sco_archives_justificatifs import JustificatifArchiver
|
||||
from app.scodoc.sco_exceptions import ScoValueError
|
||||
@ -114,7 +115,7 @@ def justificatifs(etudid: int = None, nip=None, ine=None, with_query: bool = Fal
|
||||
query?user_id=[int]
|
||||
ex query?user_id=3
|
||||
"""
|
||||
|
||||
# Récupération de l'étudiant
|
||||
etud: Identite = tools.get_etud(etudid, nip, ine)
|
||||
|
||||
if etud is None:
|
||||
@ -122,11 +123,15 @@ def justificatifs(etudid: int = None, nip=None, ine=None, with_query: bool = Fal
|
||||
404,
|
||||
message="étudiant inconnu",
|
||||
)
|
||||
|
||||
# Récupération des justificatifs de l'étudiant
|
||||
justificatifs_query = etud.justificatifs
|
||||
|
||||
# Filtrage des justificatifs en fonction de la requête
|
||||
if with_query:
|
||||
justificatifs_query = _filter_manager(request, justificatifs_query)
|
||||
|
||||
# Mise en forme des données puis retour en JSON
|
||||
data_set: list[dict] = []
|
||||
for just in justificatifs_query.all():
|
||||
data = just.to_dict(format_api=True)
|
||||
@ -147,44 +152,51 @@ def justificatifs(etudid: int = None, nip=None, ine=None, with_query: bool = Fal
|
||||
@permission_required(Permission.ScoView)
|
||||
def justificatifs_dept(dept_id: int = None, with_query: bool = False):
|
||||
""" """
|
||||
dept = Departement.query.get_or_404(dept_id)
|
||||
etuds = [etud.id for etud in dept.etudiants]
|
||||
|
||||
justificatifs_query = Justificatif.query.filter(Justificatif.etudid.in_(etuds))
|
||||
# Récupération du département et des étudiants du département
|
||||
dept: Departement = Departement.query.get_or_404(dept_id)
|
||||
etuds: list[int] = [etud.id for etud in dept.etudiants]
|
||||
|
||||
# Récupération des justificatifs des étudiants du département
|
||||
justificatifs_query: Query = Justificatif.query.filter(
|
||||
Justificatif.etudid.in_(etuds)
|
||||
)
|
||||
|
||||
# Filtrage des justificatifs
|
||||
if with_query:
|
||||
justificatifs_query = _filter_manager(request, justificatifs_query)
|
||||
justificatifs_query: Query = _filter_manager(request, justificatifs_query)
|
||||
|
||||
# Mise en forme des données et retour JSON
|
||||
data_set: list[dict] = []
|
||||
for just in justificatifs_query:
|
||||
data_set.append(_set_sems_and_groupe(just))
|
||||
data_set.append(_set_sems(just))
|
||||
|
||||
return data_set
|
||||
|
||||
|
||||
def _set_sems_and_groupe(justi: Justificatif) -> dict:
|
||||
from app.scodoc.sco_groups import get_etud_groups
|
||||
def _set_sems(justi: Justificatif) -> dict:
|
||||
"""
|
||||
_set_sems Ajoute le formsemestre associé au justificatif s'il existe
|
||||
|
||||
Si le formsemestre n'existe pas, renvoie la simple représentation du justificatif
|
||||
|
||||
Args:
|
||||
justi (Justificatif): Le justificatif
|
||||
|
||||
Returns:
|
||||
dict: La représentation de l'assiduité en dictionnaire
|
||||
"""
|
||||
# Conversion du justificatif en dictionnaire
|
||||
data = justi.to_dict(format_api=True)
|
||||
|
||||
formsemestre: FormSemestre = (
|
||||
FormSemestre.query.join(
|
||||
FormSemestreInscription,
|
||||
FormSemestre.id == FormSemestreInscription.formsemestre_id,
|
||||
)
|
||||
.filter(
|
||||
justi.date_debut <= FormSemestre.date_fin,
|
||||
justi.date_fin >= FormSemestre.date_debut,
|
||||
FormSemestreInscription.etudid == justi.etudid,
|
||||
)
|
||||
.first()
|
||||
)
|
||||
# Récupération du formsemestre de l'assiduité
|
||||
formsemestre: FormSemestre = get_formsemestre_from_data(justi.to_dict())
|
||||
# Si le formsemestre existe on l'ajoute au dictionnaire
|
||||
if formsemestre:
|
||||
data["formsemestre"] = {
|
||||
"id": formsemestre.id,
|
||||
"title": formsemestre.session_id(),
|
||||
}
|
||||
|
||||
return data
|
||||
|
||||
|
||||
@ -208,20 +220,27 @@ def _set_sems_and_groupe(justi: Justificatif) -> dict:
|
||||
@permission_required(Permission.ScoView)
|
||||
def justificatifs_formsemestre(formsemestre_id: int, with_query: bool = False):
|
||||
"""Retourne tous les justificatifs du formsemestre"""
|
||||
|
||||
# Récupération du formsemestre
|
||||
formsemestre: FormSemestre = None
|
||||
formsemestre_id = int(formsemestre_id)
|
||||
formsemestre = FormSemestre.query.filter_by(id=formsemestre_id).first()
|
||||
formsemestre: 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")
|
||||
|
||||
justificatifs_query = scass.filter_by_formsemestre(
|
||||
# Récupération des justificatifs du semestre
|
||||
justificatifs_query: Query = scass.filter_by_formsemestre(
|
||||
Justificatif.query, Justificatif, formsemestre
|
||||
)
|
||||
|
||||
# Filtrage des justificatifs
|
||||
if with_query:
|
||||
justificatifs_query = _filter_manager(request, justificatifs_query)
|
||||
justificatifs_query: Query = _filter_manager(request, justificatifs_query)
|
||||
|
||||
# Retour des justificatifs en JSON
|
||||
data_set: list[dict] = []
|
||||
for justi in justificatifs_query.all():
|
||||
data = justi.to_dict(format_api=True)
|
||||
@ -264,6 +283,8 @@ def justif_create(etudid: int = None, nip=None, ine=None):
|
||||
]
|
||||
|
||||
"""
|
||||
|
||||
# Récupération de l'étudiant
|
||||
etud: Identite = tools.get_etud(etudid, nip, ine)
|
||||
|
||||
if etud is None:
|
||||
@ -272,16 +293,22 @@ def justif_create(etudid: int = None, nip=None, ine=None):
|
||||
message="étudiant inconnu",
|
||||
)
|
||||
|
||||
# Récupération des justificatifs à créer
|
||||
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 = []
|
||||
justifs: list = []
|
||||
errors: list[dict] = []
|
||||
success: list[dict] = []
|
||||
justifs: list[Justificatif] = []
|
||||
|
||||
# énumération des justificatifs
|
||||
for i, data in enumerate(create_list):
|
||||
code, obj, justi = _create_one(data, etud)
|
||||
code: int
|
||||
obj: str | dict
|
||||
justi: Justificatif | None
|
||||
if code == 404:
|
||||
errors.append({"indice": i, "message": obj})
|
||||
else:
|
||||
@ -289,6 +316,7 @@ def justif_create(etudid: int = None, nip=None, ine=None):
|
||||
justifs.append(justi)
|
||||
scass.simple_invalidate_cache(data, etud.id)
|
||||
|
||||
# Actualisation des assiduités justifiées en fonction de tous les nouveaux justificatifs
|
||||
compute_assiduites_justified(etud.etudid, justifs)
|
||||
return {"errors": errors, "success": success}
|
||||
|
||||
@ -296,32 +324,32 @@ def justif_create(etudid: int = None, nip=None, ine=None):
|
||||
def _create_one(
|
||||
data: dict,
|
||||
etud: Identite,
|
||||
) -> tuple[int, object]:
|
||||
) -> tuple[int, object, Justificatif]:
|
||||
errors: list[str] = []
|
||||
|
||||
# -- vérifications de l'objet json --
|
||||
# cas 1 : ETAT
|
||||
etat = data.get("etat", None)
|
||||
etat: str = data.get("etat", None)
|
||||
if etat is None:
|
||||
errors.append("param 'etat': manquant")
|
||||
elif not scu.EtatJustificatif.contains(etat):
|
||||
errors.append("param 'etat': invalide")
|
||||
|
||||
etat = scu.EtatJustificatif.get(etat)
|
||||
etat: scu.EtatJustificatif = scu.EtatJustificatif.get(etat)
|
||||
|
||||
# cas 2 : date_debut
|
||||
date_debut = data.get("date_debut", None)
|
||||
date_debut: str = 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)
|
||||
deb: datetime = 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)
|
||||
date_fin: str = 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)
|
||||
fin: datetime = scu.is_iso_formated(date_fin, convert=True)
|
||||
if fin is None:
|
||||
errors.append("param 'date_fin': format invalide")
|
||||
|
||||
@ -329,7 +357,7 @@ def _create_one(
|
||||
|
||||
raison: str = data.get("raison", None)
|
||||
|
||||
external_data = data.get("external_data")
|
||||
external_data: dict = 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")
|
||||
@ -341,6 +369,7 @@ def _create_one(
|
||||
# TOUT EST OK
|
||||
|
||||
try:
|
||||
# On essaye de créer le justificatif
|
||||
nouv_justificatif: Query = Justificatif.create_justificatif(
|
||||
date_debut=deb,
|
||||
date_fin=fin,
|
||||
@ -351,6 +380,11 @@ def _create_one(
|
||||
external_data=external_data,
|
||||
)
|
||||
|
||||
# Si tout s'est bien passé on ajoute l'assiduité à la session
|
||||
# et on retourne un code 200 avec un objet possèdant le justif_id
|
||||
# ainsi que les assiduités justifiées par le dit justificatif
|
||||
|
||||
# On renvoie aussi le justificatif créé pour pour le calcul total de fin
|
||||
db.session.add(nouv_justificatif)
|
||||
db.session.commit()
|
||||
|
||||
@ -363,10 +397,7 @@ def _create_one(
|
||||
nouv_justificatif,
|
||||
)
|
||||
except ScoValueError as excp:
|
||||
return (
|
||||
404,
|
||||
excp.args[0],
|
||||
)
|
||||
return (404, excp.args[0], None)
|
||||
|
||||
|
||||
@bp.route("/justificatif/<int:justif_id>/edit", methods=["POST"])
|
||||
@ -387,53 +418,58 @@ def justif_edit(justif_id: int):
|
||||
"date_fin"?: str
|
||||
}
|
||||
"""
|
||||
|
||||
# Récupération du justificatif à modifier
|
||||
justificatif_unique: Query = Justificatif.query.filter_by(
|
||||
id=justif_id
|
||||
).first_or_404()
|
||||
|
||||
errors: list[str] = []
|
||||
data = request.get_json(force=True)
|
||||
|
||||
# Récupération des assiduités (id) précédemment justifiée par le justificatif
|
||||
avant_ids: list[int] = scass.justifies(justificatif_unique)
|
||||
# Vérifications de data
|
||||
|
||||
# Cas 1 : Etat
|
||||
if data.get("etat") is not None:
|
||||
etat = scu.EtatJustificatif.get(data.get("etat"))
|
||||
etat: scu.EtatJustificatif = scu.EtatJustificatif.get(data.get("etat"))
|
||||
if etat is None:
|
||||
errors.append("param 'etat': invalide")
|
||||
else:
|
||||
justificatif_unique.etat = etat
|
||||
|
||||
# Cas 2 : raison
|
||||
raison = data.get("raison", False)
|
||||
raison: str = data.get("raison", False)
|
||||
if raison is not False:
|
||||
justificatif_unique.raison = raison
|
||||
|
||||
deb, fin = None, None
|
||||
|
||||
# cas 3 : date_debut
|
||||
date_debut = data.get("date_debut", False)
|
||||
date_debut: str = data.get("date_debut", False)
|
||||
if date_debut is not False:
|
||||
if date_debut is None:
|
||||
errors.append("param 'date_debut': manquant")
|
||||
deb = scu.is_iso_formated(date_debut.replace(" ", "+"), convert=True)
|
||||
deb: datetime = scu.is_iso_formated(date_debut.replace(" ", "+"), convert=True)
|
||||
if deb is None:
|
||||
errors.append("param 'date_debut': format invalide")
|
||||
|
||||
# cas 4 : date_fin
|
||||
date_fin = data.get("date_fin", False)
|
||||
date_fin: str = data.get("date_fin", False)
|
||||
if date_fin is not False:
|
||||
if date_fin is None:
|
||||
errors.append("param 'date_fin': manquant")
|
||||
fin = scu.is_iso_formated(date_fin.replace(" ", "+"), convert=True)
|
||||
fin: datetime = scu.is_iso_formated(date_fin.replace(" ", "+"), convert=True)
|
||||
if fin is None:
|
||||
errors.append("param 'date_fin': format invalide")
|
||||
|
||||
# Mise à jour des dates
|
||||
# Récupération des dates précédentes si deb ou fin est None
|
||||
deb = deb if deb is not None else justificatif_unique.date_debut
|
||||
fin = fin if fin is not None else justificatif_unique.date_fin
|
||||
|
||||
external_data = data.get("external_data")
|
||||
# Mise à jour de l'external data
|
||||
external_data: dict = 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")
|
||||
@ -443,6 +479,7 @@ def justif_edit(justif_id: int):
|
||||
if fin <= deb:
|
||||
errors.append("param 'dates' : Date de début après date de fin")
|
||||
|
||||
# Mise à jour des dates du justificatif
|
||||
justificatif_unique.date_debut = deb
|
||||
justificatif_unique.date_fin = fin
|
||||
|
||||
@ -450,9 +487,14 @@ def justif_edit(justif_id: int):
|
||||
err: str = ", ".join(errors)
|
||||
return json_error(404, err)
|
||||
|
||||
# Mise à jour du justificatif
|
||||
db.session.add(justificatif_unique)
|
||||
db.session.commit()
|
||||
|
||||
# Génération du dictionnaire de retour
|
||||
# La couverture correspond
|
||||
# - aux assiduités précédemment justifiées par le justificatif
|
||||
# - aux assiduités qui sont justifiées par le justificatif modifié
|
||||
retour = {
|
||||
"couverture": {
|
||||
"avant": avant_ids,
|
||||
@ -463,7 +505,7 @@ def justif_edit(justif_id: int):
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
# Invalide le cache
|
||||
scass.simple_invalidate_cache(justificatif_unique.to_dict())
|
||||
return retour
|
||||
|
||||
@ -487,6 +529,8 @@ def justif_delete():
|
||||
|
||||
|
||||
"""
|
||||
|
||||
# Récupération des justif_ids
|
||||
justificatifs_list: list[int] = request.get_json(force=True)
|
||||
if not isinstance(justificatifs_list, list):
|
||||
return json_error(404, "Le contenu envoyé n'est pas une liste")
|
||||
@ -494,7 +538,7 @@ def justif_delete():
|
||||
output = {"errors": [], "success": []}
|
||||
|
||||
for i, ass in enumerate(justificatifs_list):
|
||||
code, msg = _delete_singular(ass, db)
|
||||
code, msg = _delete_one(ass)
|
||||
if code == 404:
|
||||
output["errors"].append({"indice": i, "message": msg})
|
||||
else:
|
||||
@ -505,22 +549,41 @@ def justif_delete():
|
||||
return output
|
||||
|
||||
|
||||
def _delete_singular(justif_id: int, database):
|
||||
justificatif_unique: Query = Justificatif.query.filter_by(id=justif_id).first()
|
||||
def _delete_one(justif_id: int) -> tuple[int, str]:
|
||||
"""
|
||||
_delete_one Supprime un justificatif
|
||||
|
||||
Args:
|
||||
justif_id (int): l'identifiant du justificatif
|
||||
|
||||
Returns:
|
||||
tuple[int, str]: code, message
|
||||
code : 200 si réussi, 404 sinon
|
||||
message : OK si réussi, message d'erreur sinon
|
||||
"""
|
||||
# Récupération du justificatif à supprimer
|
||||
justificatif_unique: Justificatif = Justificatif.query.filter_by(
|
||||
id=justif_id
|
||||
).first()
|
||||
if justificatif_unique is None:
|
||||
return (404, "Justificatif non existant")
|
||||
|
||||
# Récupération de l'archive du justificatif
|
||||
archive_name: str = justificatif_unique.fichier
|
||||
|
||||
if archive_name is not None:
|
||||
# Si elle existe : on essaye de la supprimer
|
||||
archiver: JustificatifArchiver = JustificatifArchiver()
|
||||
try:
|
||||
archiver.delete_justificatif(justificatif_unique.etudiant, archive_name)
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
# On invalide le cache
|
||||
scass.simple_invalidate_cache(justificatif_unique.to_dict())
|
||||
database.session.delete(justificatif_unique)
|
||||
# On supprime le justificatif
|
||||
db.session.delete(justificatif_unique)
|
||||
# On actualise les assiduités justifiées de l'étudiant concerné
|
||||
compute_assiduites_justified(
|
||||
justificatif_unique.etudid,
|
||||
Justificatif.query.filter_by(etudid=justificatif_unique.etudid).all(),
|
||||
@ -541,23 +604,27 @@ def justif_import(justif_id: int = None):
|
||||
"""
|
||||
Importation d'un fichier (création d'archive)
|
||||
"""
|
||||
|
||||
# On vérifie qu'un fichier a bien été envoyé
|
||||
if len(request.files) == 0:
|
||||
return json_error(404, "Il n'y a pas de fichier joint")
|
||||
|
||||
file = list(request.files.values())[0]
|
||||
if file.filename == "":
|
||||
return json_error(404, "Il n'y a pas de fichier joint")
|
||||
|
||||
# On récupère le justificatif auquel on va importer le fichier
|
||||
query: Query = Justificatif.query.filter_by(id=justif_id)
|
||||
if g.scodoc_dept:
|
||||
query = query.join(Identite).filter_by(dept_id=g.scodoc_dept_id)
|
||||
|
||||
justificatif_unique: Justificatif = query.first_or_404()
|
||||
|
||||
# Récupération de l'archive si elle existe
|
||||
archive_name: str = justificatif_unique.fichier
|
||||
|
||||
# Utilisation de l'archiver de justificatifs
|
||||
archiver: JustificatifArchiver = JustificatifArchiver()
|
||||
try:
|
||||
# On essaye de sauvegarder le fichier
|
||||
fname: str
|
||||
archive_name, fname = archiver.save_justificatif(
|
||||
justificatif_unique.etudiant,
|
||||
@ -567,6 +634,7 @@ def justif_import(justif_id: int = None):
|
||||
user_id=current_user.id,
|
||||
)
|
||||
|
||||
# On actualise l'archive du justificatif
|
||||
justificatif_unique.fichier = archive_name
|
||||
|
||||
db.session.add(justificatif_unique)
|
||||
@ -574,6 +642,7 @@ def justif_import(justif_id: int = None):
|
||||
|
||||
return {"filename": fname}
|
||||
except ScoValueError as err:
|
||||
# Si cela ne fonctionne pas on renvoie une erreur
|
||||
return json_error(404, err.args[0])
|
||||
|
||||
|
||||
@ -587,23 +656,26 @@ def justif_export(justif_id: int = None, filename: str = None):
|
||||
Retourne un fichier d'une archive d'un justificatif
|
||||
"""
|
||||
|
||||
# On récupère le justificatif concerné
|
||||
query: Query = Justificatif.query.filter_by(id=justif_id)
|
||||
if g.scodoc_dept:
|
||||
query = query.join(Identite).filter_by(dept_id=g.scodoc_dept_id)
|
||||
|
||||
justificatif_unique: Justificatif = query.first_or_404()
|
||||
|
||||
# On récupère l'archive concernée
|
||||
archive_name: str = justificatif_unique.fichier
|
||||
if archive_name is None:
|
||||
# On retourne une erreur si le justificatif n'a pas de fichiers
|
||||
return json_error(404, "le justificatif ne possède pas de fichier")
|
||||
|
||||
# On récupère le fichier et le renvoie en une réponse déjà formée
|
||||
archiver: JustificatifArchiver = JustificatifArchiver()
|
||||
|
||||
try:
|
||||
return archiver.get_justificatif_file(
|
||||
archive_name, justificatif_unique.etudiant, filename
|
||||
)
|
||||
except ScoValueError as err:
|
||||
# On retourne une erreur json si jamais il y a un problème
|
||||
return json_error(404, err.args[0])
|
||||
|
||||
|
||||
@ -616,7 +688,6 @@ def justif_export(justif_id: int = None, filename: str = None):
|
||||
def justif_remove(justif_id: int = None):
|
||||
"""
|
||||
Supression d'un fichier ou d'une archive
|
||||
# TOTALK: Doc, expliquer les noms coté server
|
||||
{
|
||||
"remove": <"all"/"list">
|
||||
|
||||
@ -627,31 +698,41 @@ def justif_remove(justif_id: int = None):
|
||||
}
|
||||
"""
|
||||
|
||||
# On récupère le dictionnaire
|
||||
data: dict = request.get_json(force=True)
|
||||
|
||||
# On récupère le justificatif concerné
|
||||
query: Query = Justificatif.query.filter_by(id=justif_id)
|
||||
if g.scodoc_dept:
|
||||
query = query.join(Identite).filter_by(dept_id=g.scodoc_dept_id)
|
||||
|
||||
justificatif_unique: Justificatif = query.first_or_404()
|
||||
|
||||
# On récupère l'archive
|
||||
archive_name: str = justificatif_unique.fichier
|
||||
if archive_name is None:
|
||||
# On retourne une erreur si le justificatif n'a pas de fichiers
|
||||
return json_error(404, "le justificatif ne possède pas de fichier")
|
||||
|
||||
# On regarde le type de suppression (all ou list)
|
||||
# Si all : on supprime tous les fichiers
|
||||
# Si list : on supprime les fichiers dont le nom est dans la liste
|
||||
remove: str = data.get("remove")
|
||||
if remove is None or remove not in ("all", "list"):
|
||||
return json_error(404, "param 'remove': Valeur invalide")
|
||||
|
||||
# On récupère l'archiver et l'étudiant
|
||||
archiver: JustificatifArchiver = JustificatifArchiver()
|
||||
etud = justificatif_unique.etudiant
|
||||
try:
|
||||
if remove == "all":
|
||||
# Suppression de toute l'archive du justificatif
|
||||
archiver.delete_justificatif(etud, archive_name=archive_name)
|
||||
justificatif_unique.fichier = None
|
||||
db.session.add(justificatif_unique)
|
||||
db.session.commit()
|
||||
|
||||
else:
|
||||
# Suppression des fichiers dont le nom se trouve dans la liste "filenames"
|
||||
for fname in data.get("filenames", []):
|
||||
archiver.delete_justificatif(
|
||||
etud,
|
||||
@ -659,6 +740,7 @@ def justif_remove(justif_id: int = None):
|
||||
filename=fname,
|
||||
)
|
||||
|
||||
# Si il n'y a plus de fichiers dans l'archive, on la supprime
|
||||
if len(archiver.list_justificatifs(archive_name, etud)) == 0:
|
||||
archiver.delete_justificatif(etud, archive_name)
|
||||
justificatif_unique.fichier = None
|
||||
@ -666,8 +748,10 @@ def justif_remove(justif_id: int = None):
|
||||
db.session.commit()
|
||||
|
||||
except ScoValueError as err:
|
||||
# On retourne une erreur json si jamais il y a eu un problème
|
||||
return json_error(404, err.args[0])
|
||||
|
||||
# On retourne une réponse "removed" si tout s'est bien passé
|
||||
return {"response": "removed"}
|
||||
|
||||
|
||||
@ -682,29 +766,36 @@ def justif_list(justif_id: int = None):
|
||||
Liste les fichiers du justificatif
|
||||
"""
|
||||
|
||||
# Récupération du justificatif concerné
|
||||
query: Query = Justificatif.query.filter_by(id=justif_id)
|
||||
if g.scodoc_dept:
|
||||
query = query.join(Identite).filter_by(dept_id=g.scodoc_dept_id)
|
||||
|
||||
justificatif_unique: Justificatif = query.first_or_404()
|
||||
|
||||
# Récupération de l'archive avec l'archiver
|
||||
archive_name: str = justificatif_unique.fichier
|
||||
|
||||
filenames: list[str] = []
|
||||
|
||||
archiver: JustificatifArchiver = JustificatifArchiver()
|
||||
if archive_name is not None:
|
||||
filenames = archiver.list_justificatifs(
|
||||
archive_name, justificatif_unique.etudiant
|
||||
)
|
||||
|
||||
# Préparation du retour
|
||||
# - total : le nombre total de fichier du justificatif
|
||||
# - filenames : le nom des fichiers visible par l'utilisateur
|
||||
retour = {"total": len(filenames), "filenames": []}
|
||||
|
||||
# Pour chaque nom de fichier on vérifie
|
||||
# - Si l'utilisateur qui a importé le fichier est le même que
|
||||
# l'utilisateur qui a demandé la liste des fichiers
|
||||
# - Ou si l'utilisateur qui a demandé la liste possède la permission AbsJustifView
|
||||
# Si c'est le cas alors on ajoute à la liste des fichiers visibles
|
||||
for filename in filenames:
|
||||
if int(filename[1]) == current_user.id or current_user.has_permission(
|
||||
Permission.AbsJustifView
|
||||
):
|
||||
retour["filenames"].append(filename[0])
|
||||
# On renvoie le total et la liste des fichiers visibles
|
||||
return retour
|
||||
|
||||
|
||||
@ -720,44 +811,45 @@ def justif_justifies(justif_id: int = None):
|
||||
Liste assiduite_id justifiées par le justificatif
|
||||
"""
|
||||
|
||||
# On récupère le justificatif concerné
|
||||
query: Query = Justificatif.query.filter_by(id=justif_id)
|
||||
if g.scodoc_dept:
|
||||
query = query.join(Identite).filter_by(dept_id=g.scodoc_dept_id)
|
||||
|
||||
justificatif_unique: Justificatif = query.first_or_404()
|
||||
|
||||
# On récupère la liste des assiduités justifiées par le justificatif
|
||||
assiduites_list: list[int] = scass.justifies(justificatif_unique)
|
||||
|
||||
# On la renvoie
|
||||
return assiduites_list
|
||||
|
||||
|
||||
# -- Utils --
|
||||
|
||||
|
||||
def _filter_manager(requested, justificatifs_query):
|
||||
def _filter_manager(requested, justificatifs_query: Query):
|
||||
"""
|
||||
Retourne les justificatifs entrés filtrés en fonction de la request
|
||||
"""
|
||||
# cas 1 : etat justificatif
|
||||
etat = requested.args.get("etat")
|
||||
etat: str = requested.args.get("etat")
|
||||
if etat is not None:
|
||||
justificatifs_query = scass.filter_justificatifs_by_etat(
|
||||
justificatifs_query: Query = scass.filter_justificatifs_by_etat(
|
||||
justificatifs_query, etat
|
||||
)
|
||||
|
||||
# cas 2 : date de début
|
||||
deb = requested.args.get("date_debut", "").replace(" ", "+")
|
||||
deb: str = 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)
|
||||
fin: str = requested.args.get("date_fin", "").replace(" ", "+")
|
||||
fin: datetime = scu.is_iso_formated(fin, True)
|
||||
|
||||
if (deb, fin) != (None, None):
|
||||
justificatifs_query: Query = scass.filter_by_date(
|
||||
justificatifs_query, Justificatif, deb, fin
|
||||
)
|
||||
|
||||
# cas 4 : user_id
|
||||
user_id = requested.args.get("user_id", False)
|
||||
if user_id is not False:
|
||||
justificatifs_query: Query = scass.filter_by_user_id(
|
||||
@ -778,12 +870,13 @@ def _filter_manager(requested, justificatifs_query):
|
||||
except ValueError:
|
||||
formsemestre = None
|
||||
|
||||
# cas 6 : order (retourne les justificatifs par ordre décroissant de date_debut)
|
||||
order = requested.args.get("order", None)
|
||||
if order is not None:
|
||||
justificatifs_query: Query = justificatifs_query.order_by(
|
||||
Justificatif.date_debut.desc()
|
||||
)
|
||||
|
||||
# cas 7 : courant (retourne uniquement les justificatifs de l'année scolaire courante)
|
||||
courant = requested.args.get("courant", None)
|
||||
if courant is not None:
|
||||
annee: int = scu.annee_scolaire()
|
||||
@ -793,6 +886,7 @@ def _filter_manager(requested, justificatifs_query):
|
||||
Justificatif.date_fin <= scu.date_fin_anne_scolaire(annee),
|
||||
)
|
||||
|
||||
# cas 8 : group_id filtre les justificatifs d'un groupe d'étudiant
|
||||
group_id = requested.args.get("group_id", None)
|
||||
if group_id is not None:
|
||||
try:
|
||||
|
@ -37,7 +37,9 @@ import datetime
|
||||
|
||||
|
||||
class TimeField(StringField):
|
||||
"""HTML5 time input."""
|
||||
"""HTML5 time input.
|
||||
tiré de : https://gist.github.com/tachyondecay/6016d32f65a996d0d94f
|
||||
"""
|
||||
|
||||
widget = TimeInput()
|
||||
|
||||
|
@ -82,6 +82,7 @@ class Assiduite(db.Model):
|
||||
etat = self.etat
|
||||
user: User = None
|
||||
if format_api:
|
||||
# format api utilise les noms "present,absent,retard" au lieu des int
|
||||
etat = EtatAssiduite.inverse().get(self.etat).name
|
||||
if self.user_id is not None:
|
||||
user = db.session.get(User, self.user_id)
|
||||
@ -345,15 +346,10 @@ def is_period_conflicting(
|
||||
avec les justificatifs ou assiduites déjà présentes
|
||||
"""
|
||||
|
||||
# On s'assure que les dates soient avec TimeZone
|
||||
date_debut = localize_datetime(date_debut)
|
||||
date_fin = localize_datetime(date_fin)
|
||||
|
||||
if (
|
||||
collection.filter_by(date_debut=date_debut, date_fin=date_fin).first()
|
||||
is not None
|
||||
):
|
||||
return True
|
||||
|
||||
count: int = collection.filter(
|
||||
collection_cls.date_debut < date_fin, collection_cls.date_fin > date_debut
|
||||
).count()
|
||||
@ -375,19 +371,26 @@ def compute_assiduites_justified(
|
||||
Returns:
|
||||
list[int]: la liste des assiduités qui ont été justifiées.
|
||||
"""
|
||||
# Si on ne donne pas de justificatifs on prendra par défaut tous les justificatifs de l'étudiant
|
||||
if justificatifs is None:
|
||||
justificatifs: Justificatif = Justificatif.query.filter_by(etudid=etudid).all()
|
||||
justificatifs: list[Justificatif] = Justificatif.query.filter_by(
|
||||
etudid=etudid
|
||||
).all()
|
||||
|
||||
# On ne prend que les justificatifs valides
|
||||
justificatifs = [j for j in justificatifs if j.etat == EtatJustificatif.VALIDE]
|
||||
|
||||
# On récupère les assiduités de l'étudiant
|
||||
assiduites: Assiduite = Assiduite.query.filter_by(etudid=etudid)
|
||||
|
||||
assiduites_justifiees: list[int] = []
|
||||
|
||||
for assi in assiduites:
|
||||
# On ne justifie pas les Présences
|
||||
if assi.etat == EtatAssiduite.PRESENT:
|
||||
continue
|
||||
|
||||
# On récupère les justificatifs qui justifient l'assiduité `assi`
|
||||
assi_justificatifs = Justificatif.query.filter(
|
||||
Justificatif.etudid == assi.etudid,
|
||||
Justificatif.date_debut <= assi.date_debut,
|
||||
@ -395,21 +398,39 @@ def compute_assiduites_justified(
|
||||
Justificatif.etat == EtatJustificatif.VALIDE,
|
||||
).all()
|
||||
|
||||
# Si au moins un justificatif possède une période qui couvre l'assiduité
|
||||
if any(
|
||||
assi.date_debut >= j.date_debut and assi.date_fin <= j.date_fin
|
||||
for j in justificatifs + assi_justificatifs
|
||||
):
|
||||
# On justifie l'assiduité
|
||||
# On ajoute l'id de l'assiduité à la liste des assiduités justifiées
|
||||
assi.est_just = True
|
||||
assiduites_justifiees.append(assi.assiduite_id)
|
||||
db.session.add(assi)
|
||||
elif reset:
|
||||
# Si le paramètre reset est Vrai alors les assiduités non justifiées
|
||||
# sont remise en "non justifiée"
|
||||
assi.est_just = False
|
||||
db.session.add(assi)
|
||||
# On valide la session
|
||||
db.session.commit()
|
||||
# On renvoie la liste des assiduite_id des assiduités justifiées
|
||||
return assiduites_justifiees
|
||||
|
||||
|
||||
def get_assiduites_justif(assiduite_id: int, long: bool):
|
||||
def get_assiduites_justif(assiduite_id: int, long: bool) -> list[int | dict]:
|
||||
"""
|
||||
get_assiduites_justif Récupération des justificatifs d'une assiduité
|
||||
|
||||
Args:
|
||||
assiduite_id (int): l'identifiant de l'assiduité
|
||||
long (bool): Retourner des dictionnaires à la place
|
||||
des identifiants des justificatifs
|
||||
|
||||
Returns:
|
||||
list[int | dict]: La liste des justificatifs (par défaut uniquement les identifiants, sinon les Dict si long est vrai)
|
||||
"""
|
||||
assi: Assiduite = Assiduite.query.get_or_404(assiduite_id)
|
||||
return get_justifs_from_date(assi.etudid, assi.date_debut, assi.date_fin, long)
|
||||
|
||||
@ -420,20 +441,57 @@ def get_justifs_from_date(
|
||||
date_fin: datetime,
|
||||
long: bool = False,
|
||||
valid: bool = False,
|
||||
):
|
||||
) -> list[int | dict]:
|
||||
"""
|
||||
get_justifs_from_date Récupération des justificatifs couvrant une période pour un étudiant donné
|
||||
|
||||
Args:
|
||||
etudid (int): l'identifiant de l'étudiant
|
||||
date_debut (datetime): la date de début (datetime avec timezone)
|
||||
date_fin (datetime): la date de fin (datetime avec timezone)
|
||||
long (bool, optional): Définition de la sortie.
|
||||
Vrai pour avoir les dictionnaires des justificatifs.
|
||||
Faux pour avoir uniquement les identifiants
|
||||
Defaults to False.
|
||||
valid (bool, optional): Filtre pour n'avoir que les justificatifs valide.
|
||||
Si vrai : le retour ne contiendra que des justificatifs valides
|
||||
Sinon le retour contiendra tout type de justificatifs
|
||||
Defaults to False.
|
||||
|
||||
Returns:
|
||||
list[int | dict]: La liste des justificatifs (par défaut uniquement les identifiants, sinon les Dict si long est vrai)
|
||||
"""
|
||||
# On récupère les justificatifs d'un étudiant couvrant la période donnée
|
||||
justifs: Query = Justificatif.query.filter(
|
||||
Justificatif.etudid == etudid,
|
||||
Justificatif.date_debut <= date_debut,
|
||||
Justificatif.date_fin >= date_fin,
|
||||
)
|
||||
|
||||
# si valide est vrai alors on filtre pour n'avoir que les justificatifs valide
|
||||
if valid:
|
||||
justifs = justifs.filter(Justificatif.etat == EtatJustificatif.VALIDE)
|
||||
|
||||
# On renvoie la liste des id des justificatifs si long est Faux, sinon on renvoie les dicts des justificatifs
|
||||
return [j.justif_id if not long else j.to_dict(True) for j in justifs]
|
||||
|
||||
|
||||
def get_formsemestre_from_data(data: dict[str, datetime | int]) -> FormSemestre:
|
||||
"""
|
||||
get_formsemestre_from_data récupère un formsemestre en fonction des données passées
|
||||
|
||||
Args:
|
||||
data (dict[str, datetime | int]): Une réprésentation simplifiée d'une assiduité ou d'un justificatif
|
||||
|
||||
data = {
|
||||
"etudid" : int,
|
||||
"date_debut": datetime (tz),
|
||||
"date_fin": datetime (tz),
|
||||
}
|
||||
|
||||
Returns:
|
||||
FormSemestre: Le formsemestre trouvé ou None
|
||||
"""
|
||||
return (
|
||||
FormSemestre.query.join(
|
||||
FormSemestreInscription,
|
||||
|
@ -755,9 +755,7 @@ function isConflictSameAsPeriod(conflict, period = undefined) {
|
||||
* @returns {Date} la date sélectionnée
|
||||
*/
|
||||
function getDate() {
|
||||
const date = new Date(
|
||||
document.querySelector("#tl_date").getAttribute("value")
|
||||
);
|
||||
const date = new Date(document.querySelector("#tl_date").value);
|
||||
date.setHours(0, 0, 0, 0);
|
||||
return date;
|
||||
}
|
||||
@ -1652,36 +1650,33 @@ function fastJustify(assiduite) {
|
||||
fin: new moment.tz(assiduite.date_fin, TIMEZONE),
|
||||
};
|
||||
const action = (justifs) => {
|
||||
if (justifs.length > 0) {
|
||||
justifyAssiduite(assiduite.assiduite_id, !assiduite.est_just);
|
||||
} else {
|
||||
//créer un nouveau justificatif
|
||||
// Afficher prompt -> demander raison et état
|
||||
//créer un nouveau justificatif
|
||||
// Afficher prompt -> demander raison et état
|
||||
|
||||
const success = () => {
|
||||
const raison = document.getElementById("promptText").value;
|
||||
const etat = document.getElementById("promptSelect").value;
|
||||
const success = () => {
|
||||
const raison = document.getElementById("promptText").value;
|
||||
const etat = document.getElementById("promptSelect").value;
|
||||
|
||||
//créer justificatif
|
||||
//créer justificatif
|
||||
|
||||
const justif = {
|
||||
date_debut: new moment.tz(assiduite.date_debut, TIMEZONE).format(),
|
||||
date_fin: new moment.tz(assiduite.date_fin, TIMEZONE).format(),
|
||||
raison: raison,
|
||||
etat: etat,
|
||||
};
|
||||
|
||||
createJustificatif(justif);
|
||||
|
||||
generateAllEtudRow();
|
||||
try {
|
||||
loadAll();
|
||||
} catch {}
|
||||
const justif = {
|
||||
date_debut: new moment.tz(assiduite.date_debut, TIMEZONE).format(),
|
||||
date_fin: new moment.tz(assiduite.date_fin, TIMEZONE).format(),
|
||||
raison: raison,
|
||||
etat: etat,
|
||||
};
|
||||
|
||||
const content = document.createElement("fieldset");
|
||||
createJustificatif(justif);
|
||||
|
||||
const htmlPrompt = `<legend>Entrez l'état du justificatif :</legend>
|
||||
generateAllEtudRow();
|
||||
try {
|
||||
loadAll();
|
||||
} catch {}
|
||||
};
|
||||
|
||||
const content = document.createElement("fieldset");
|
||||
|
||||
const htmlPrompt = `<legend>Entrez l'état du justificatif :</legend>
|
||||
<select name="promptSelect" id="promptSelect" required>
|
||||
<option value="valide">Valide</option>
|
||||
<option value="attente">En Attente de validation</option>
|
||||
@ -1692,16 +1687,15 @@ function fastJustify(assiduite) {
|
||||
<textarea type="text" placeholder="Explication du justificatif (non obligatoire)" id="promptText" style="width:100%;"></textarea>
|
||||
`;
|
||||
|
||||
content.innerHTML = htmlPrompt;
|
||||
content.innerHTML = htmlPrompt;
|
||||
|
||||
openPromptModal(
|
||||
"Nouveau justificatif (Rapide)",
|
||||
content,
|
||||
success,
|
||||
() => {},
|
||||
"#7059FF"
|
||||
);
|
||||
}
|
||||
openPromptModal(
|
||||
"Nouveau justificatif (Rapide)",
|
||||
content,
|
||||
success,
|
||||
() => {},
|
||||
"#7059FF"
|
||||
);
|
||||
};
|
||||
if (assiduite.etudid) {
|
||||
getJustificatifFromPeriod(period, assiduite.etudid, action);
|
||||
@ -1804,7 +1798,9 @@ function getModuleImpl(assiduite) {
|
||||
assiduite.external_data != null &&
|
||||
assiduite.external_data.hasOwnProperty("module")
|
||||
) {
|
||||
return assiduite.external_data.module;
|
||||
return assiduite.external_data.module == "Autre"
|
||||
? "Tout module"
|
||||
: assiduite.external_data.module;
|
||||
} else {
|
||||
return "Pas de module";
|
||||
}
|
||||
|
@ -345,8 +345,8 @@
|
||||
let assi = Object.values(assiduites).flat().filter((a) => { return a.assiduite_id == obj_id })[0]
|
||||
li.addEventListener('click', () => {
|
||||
|
||||
if (assiduite && !assiduite[0].est_just && assiduite[0].etat != "PRESENT") {
|
||||
fastJustify(assiduite[0])
|
||||
if (assi && !assi.est_just && assi.etat != "PRESENT") {
|
||||
fastJustify(assi)
|
||||
} else {
|
||||
openAlertModal("Erreur", document.createTextNode("L'assiduité est déjà justifiée."))
|
||||
}
|
||||
|
@ -160,6 +160,8 @@ class HTMLBuilder:
|
||||
@permission_required(Permission.AbsChange)
|
||||
def bilan_dept():
|
||||
"""Gestionnaire assiduités, page principale"""
|
||||
|
||||
# Préparation de la page
|
||||
H = [
|
||||
html_sco_header.sco_header(
|
||||
page_title="Saisie de l'assiduité",
|
||||
@ -183,7 +185,10 @@ def bilan_dept():
|
||||
"""<p class="help">Pour signaler, annuler ou justifier l'assiduité d'un seul étudiant,
|
||||
choisissez d'abord la personne concernée :</p>"""
|
||||
)
|
||||
# Ajout de la barre de recherche d'étudiant (redirection vers bilan etud)
|
||||
H.append(sco_find_etud.form_search_etud(dest_url="assiduites.bilan_etud"))
|
||||
|
||||
# Gestion des billets d'absences
|
||||
if current_user.has_permission(
|
||||
Permission.AbsChange
|
||||
) and sco_preferences.get_preference("handle_billets_abs"):
|
||||
@ -195,19 +200,23 @@ def bilan_dept():
|
||||
</li></ul>
|
||||
"""
|
||||
)
|
||||
|
||||
# Récupération des années d'étude du département
|
||||
# (afin de sélectionner une année)
|
||||
dept: Departement = Departement.query.filter_by(id=g.scodoc_dept_id).first()
|
||||
annees: list[int] = sorted(
|
||||
[f.date_debut.year for f in dept.formsemestres],
|
||||
reverse=True,
|
||||
)
|
||||
|
||||
annee = scu.annee_scolaire()
|
||||
|
||||
annee = scu.annee_scolaire() # Année courante, sera utilisée par défaut
|
||||
# Génération d'une liste "json" d'années
|
||||
annees_str: str = "["
|
||||
for ann in annees:
|
||||
annees_str += f"{ann},"
|
||||
annees_str += "]"
|
||||
|
||||
# Récupération d'un formsemestre
|
||||
# (pour n'afficher que les assiduites/justificatifs liés au formsemestre)
|
||||
formsemestre_id = request.args.get("formsemestre_id", "")
|
||||
if formsemestre_id:
|
||||
try:
|
||||
@ -216,6 +225,7 @@ def bilan_dept():
|
||||
except AttributeError:
|
||||
formsemestre_id = ""
|
||||
|
||||
# Peuplement du template jinja
|
||||
H.append(
|
||||
render_template(
|
||||
"assiduites/pages/bilan_dept.j2",
|
||||
@ -230,49 +240,6 @@ def bilan_dept():
|
||||
return "\n".join(H)
|
||||
|
||||
|
||||
# @bp.route("/ListeSemestre")
|
||||
# @scodoc
|
||||
# @permission_required(Permission.ScoView)
|
||||
# def liste_assiduites_formsemestre():
|
||||
# """
|
||||
# liste_assiduites_etud Affichage de toutes les assiduites et justificatifs d'un etudiant
|
||||
# Args:
|
||||
# etudid (int): l'identifiant de l'étudiant
|
||||
|
||||
# Returns:
|
||||
# str: l'html généré
|
||||
# """
|
||||
|
||||
# formsemestre_id = request.args.get("formsemestre_id", -1)
|
||||
# formsemestre: FormSemestre = FormSemestre.query.get_or_404(formsemestre_id)
|
||||
# if formsemestre.dept_id != g.scodoc_dept_id:
|
||||
# abort(404, "FormSemestre inexistant dans ce département")
|
||||
|
||||
# header: str = html_sco_header.sco_header(
|
||||
# page_title="Liste des assiduités du semestre",
|
||||
# init_qtip=True,
|
||||
# javascripts=[
|
||||
# "js/assiduites.js",
|
||||
# "libjs/moment-2.29.4.min.js",
|
||||
# "libjs/moment-timezone.js",
|
||||
# ],
|
||||
# cssstyles=CSSSTYLES
|
||||
# + [
|
||||
# "css/assiduites.css",
|
||||
# ],
|
||||
# )
|
||||
|
||||
# return HTMLBuilder(
|
||||
# header,
|
||||
# render_template(
|
||||
# "assiduites/pages/liste_semestre.j2",
|
||||
# sco=ScoData(formsemestre=formsemestre),
|
||||
# sem=formsemestre.titre_annee(),
|
||||
# formsemestre_id=formsemestre.id,
|
||||
# ),
|
||||
# ).build()
|
||||
|
||||
|
||||
@bp.route("/SignaleAssiduiteEtud")
|
||||
@scodoc
|
||||
@permission_required(Permission.AbsChange)
|
||||
@ -287,22 +254,22 @@ def signal_assiduites_etud():
|
||||
str: l'html généré
|
||||
"""
|
||||
|
||||
# Récupération de l'étudiant concerné
|
||||
etudid = request.args.get("etudid", -1)
|
||||
etud: Identite = Identite.query.get_or_404(etudid)
|
||||
if etud.dept_id != g.scodoc_dept_id:
|
||||
abort(404, "étudiant inexistant dans ce département")
|
||||
|
||||
# Récupération de la date (par défaut la date du jour)
|
||||
date = request.args.get("date", datetime.date.today().isoformat())
|
||||
|
||||
# gestion évaluations
|
||||
# gestion évaluations (Appel à la page depuis les évaluations)
|
||||
|
||||
saisie_eval: bool = request.args.get("saisie_eval") is not None
|
||||
|
||||
date_deb: str = request.args.get("date_deb")
|
||||
date_fin: str = request.args.get("date_fin")
|
||||
moduleimpl_id: int = request.args.get("moduleimpl_id", "")
|
||||
evaluation_id: int = request.args.get("evaluation_id")
|
||||
|
||||
redirect_url: str = (
|
||||
"#"
|
||||
if not saisie_eval
|
||||
@ -313,6 +280,7 @@ def signal_assiduites_etud():
|
||||
)
|
||||
)
|
||||
|
||||
# Préparation de la page (Header)
|
||||
header: str = html_sco_header.sco_header(
|
||||
page_title="Saisie assiduité",
|
||||
init_qtip=True,
|
||||
@ -335,11 +303,14 @@ def signal_assiduites_etud():
|
||||
"assi_afternoon_time", "18:00:00"
|
||||
)
|
||||
|
||||
# Gestion du selecteur de moduleimpl (pour le tableau différé)
|
||||
select = f"""
|
||||
<select class="dynaSelect">
|
||||
{render_template("assiduites/widgets/simplemoduleimpl_select.j2")}
|
||||
</select>
|
||||
"""
|
||||
|
||||
# Génération de la page
|
||||
return HTMLBuilder(
|
||||
header,
|
||||
_mini_timeline(),
|
||||
@ -381,13 +352,16 @@ def liste_assiduites_etud():
|
||||
str: l'html généré
|
||||
"""
|
||||
|
||||
# Récupération de l'étudiant concerné
|
||||
etudid = request.args.get("etudid", -1)
|
||||
etud: Identite = Identite.query.get_or_404(etudid)
|
||||
if etud.dept_id != g.scodoc_dept_id:
|
||||
abort(404, "étudiant inexistant dans ce département")
|
||||
|
||||
# Gestion d'une assiduité unique (redirigé depuis le calendrier)
|
||||
assiduite_id: int = request.args.get("assiduite_id", -1)
|
||||
|
||||
# Préparation de la page
|
||||
header: str = html_sco_header.sco_header(
|
||||
page_title=f"Assiduité de {etud.nomprenom}",
|
||||
init_qtip=True,
|
||||
@ -401,7 +375,7 @@ def liste_assiduites_etud():
|
||||
"css/assiduites.css",
|
||||
],
|
||||
)
|
||||
|
||||
# Peuplement du template jinja
|
||||
return HTMLBuilder(
|
||||
header,
|
||||
render_template(
|
||||
@ -429,12 +403,13 @@ def bilan_etud():
|
||||
Returns:
|
||||
str: l'html généré
|
||||
"""
|
||||
|
||||
# Récupération de l'étudiant
|
||||
etudid = request.args.get("etudid", -1)
|
||||
etud: Identite = Identite.query.get_or_404(etudid)
|
||||
if etud.dept_id != g.scodoc_dept_id:
|
||||
abort(404, "étudiant inexistant dans ce département")
|
||||
|
||||
# Préparation de la page (header)
|
||||
header: str = html_sco_header.sco_header(
|
||||
page_title=f"Bilan de l'assiduité de {etud.nomprenom}",
|
||||
init_qtip=True,
|
||||
@ -449,13 +424,16 @@ def bilan_etud():
|
||||
],
|
||||
)
|
||||
|
||||
# Gestion des dates du bilan (par défaut l'année scolaire)
|
||||
date_debut: str = f"{scu.annee_scolaire()}-09-01"
|
||||
date_fin: str = f"{scu.annee_scolaire()+1}-06-30"
|
||||
|
||||
# Récupération de la métrique d'assiduité
|
||||
assi_metric = scu.translate_assiduites_metric(
|
||||
sco_preferences.get_preference("assi_metrique", dept_id=g.scodoc_dept_id),
|
||||
)
|
||||
|
||||
# Génération de la page
|
||||
return HTMLBuilder(
|
||||
header,
|
||||
render_template(
|
||||
@ -486,11 +464,13 @@ def ajout_justificatif_etud():
|
||||
str: l'html généré
|
||||
"""
|
||||
|
||||
# Récupération de l'étudiant concerné
|
||||
etudid = request.args.get("etudid", -1)
|
||||
etud: Identite = Identite.query.get_or_404(etudid)
|
||||
if etud.dept_id != g.scodoc_dept_id:
|
||||
abort(404, "étudiant inexistant dans ce département")
|
||||
|
||||
# Préparation de la page (header)
|
||||
header: str = html_sco_header.sco_header(
|
||||
page_title="Justificatifs",
|
||||
init_qtip=True,
|
||||
@ -505,6 +485,7 @@ def ajout_justificatif_etud():
|
||||
],
|
||||
)
|
||||
|
||||
# Peuplement du template jinja
|
||||
return HTMLBuilder(
|
||||
header,
|
||||
render_template(
|
||||
@ -533,11 +514,13 @@ def calendrier_etud():
|
||||
str: l'html généré
|
||||
"""
|
||||
|
||||
# Récupération de l'étudiant
|
||||
etudid = request.args.get("etudid", -1)
|
||||
etud: Identite = Identite.query.get_or_404(etudid)
|
||||
if etud.dept_id != g.scodoc_dept_id:
|
||||
abort(404, "étudiant inexistant dans ce département")
|
||||
|
||||
# Préparation de la page
|
||||
header: str = html_sco_header.sco_header(
|
||||
page_title="Calendrier de l'assiduité",
|
||||
init_qtip=True,
|
||||
@ -552,16 +535,20 @@ def calendrier_etud():
|
||||
],
|
||||
)
|
||||
|
||||
# Récupération des années d'étude de l'étudiant
|
||||
annees: list[int] = sorted(
|
||||
[ins.formsemestre.date_debut.year for ins in etud.formsemestre_inscriptions],
|
||||
reverse=True,
|
||||
)
|
||||
|
||||
# Transformation en une liste "json"
|
||||
# (sera utilisé pour générer le selecteur d'année)
|
||||
annees_str: str = "["
|
||||
for ann in annees:
|
||||
annees_str += f"{ann},"
|
||||
annees_str += "]"
|
||||
|
||||
# Peuplement du template jinja
|
||||
return HTMLBuilder(
|
||||
header,
|
||||
render_template(
|
||||
@ -585,11 +572,11 @@ def signal_assiduites_group():
|
||||
Returns:
|
||||
str: l'html généré
|
||||
"""
|
||||
# Récupération des paramètres de l'url
|
||||
formsemestre_id: int = request.args.get("formsemestre_id", -1)
|
||||
moduleimpl_id: int = request.args.get("moduleimpl_id")
|
||||
date: str = request.args.get("jour", datetime.date.today().isoformat())
|
||||
group_ids: list[int] = request.args.get("group_ids", None)
|
||||
|
||||
if group_ids is None:
|
||||
group_ids = []
|
||||
else:
|
||||
@ -601,12 +588,14 @@ def signal_assiduites_group():
|
||||
moduleimpl_id = int(moduleimpl_id)
|
||||
except (TypeError, ValueError):
|
||||
moduleimpl_id = None
|
||||
|
||||
# Vérification du formsemestre_id
|
||||
try:
|
||||
formsemestre_id = int(formsemestre_id)
|
||||
except (TypeError, ValueError):
|
||||
formsemestre_id = None
|
||||
|
||||
# Gestion des groupes
|
||||
groups_infos = sco_groups_view.DisplayedGroupsInfos(
|
||||
group_ids, moduleimpl_id=moduleimpl_id, formsemestre_id=formsemestre_id
|
||||
)
|
||||
@ -617,10 +606,6 @@ def signal_assiduites_group():
|
||||
+ html_sco_header.sco_footer()
|
||||
)
|
||||
|
||||
# --- URL DEFAULT ---
|
||||
|
||||
base_url: str = f"SignalAssiduiteGr?date={date}&{groups_infos.groups_query_args}"
|
||||
|
||||
# --- Filtrage par formsemestre ---
|
||||
formsemestre_id = groups_infos.formsemestre_id
|
||||
|
||||
@ -628,17 +613,22 @@ def signal_assiduites_group():
|
||||
if formsemestre.dept_id != g.scodoc_dept_id:
|
||||
abort(404, "groupes inexistants dans ce département")
|
||||
|
||||
# Vérification du forçage du module
|
||||
require_module = sco_preferences.get_preference("forcer_module", formsemestre_id)
|
||||
|
||||
# Récupération des étudiants des groupes
|
||||
etuds = [
|
||||
sco_etud.get_etud_info(etudid=m["etudid"], filled=True)[0]
|
||||
for m in groups_infos.members
|
||||
]
|
||||
|
||||
# --- Vérification de la date ---
|
||||
|
||||
real_date = scu.is_iso_formated(date, True).date()
|
||||
|
||||
if real_date < formsemestre.date_debut or real_date > formsemestre.date_fin:
|
||||
# Si le jour est hors semestre, indiquer une erreur
|
||||
|
||||
# Formatage des dates pour le message d'erreur
|
||||
real_str = real_date.strftime("%d/%m/%Y")
|
||||
form_deb = formsemestre.date_debut.strftime("%d/%m/%Y")
|
||||
form_fin = formsemestre.date_fin.strftime("%d/%m/%Y")
|
||||
@ -662,8 +652,7 @@ def signal_assiduites_group():
|
||||
# Si aucun etudiant n'est inscrit au module choisi...
|
||||
moduleimpl_id = None
|
||||
|
||||
# --- Génération de l'HTML ---
|
||||
sem = formsemestre.to_dict()
|
||||
# Récupération du nom des/du groupe(s)
|
||||
|
||||
if groups_infos.tous_les_etuds_du_sem:
|
||||
gr_tit = "en"
|
||||
@ -676,12 +665,15 @@ def signal_assiduites_group():
|
||||
grp + ' <span class="fontred">' + groups_infos.groups_titles + "</span>"
|
||||
)
|
||||
|
||||
# --- Génération de l'HTML ---
|
||||
|
||||
header: str = html_sco_header.sco_header(
|
||||
page_title="Saisie journalière des assiduités",
|
||||
init_qtip=True,
|
||||
javascripts=html_sco_header.BOOTSTRAP_MULTISELECT_JS
|
||||
+ [
|
||||
# Voir fonctionnement JS
|
||||
# XXX Retirer moment
|
||||
"js/etud_info.js",
|
||||
"js/groups_view.js",
|
||||
"js/assiduites.js",
|
||||
@ -694,6 +686,10 @@ def signal_assiduites_group():
|
||||
],
|
||||
)
|
||||
|
||||
# Récupération du semestre en dictionnaire
|
||||
sem = formsemestre.to_dict()
|
||||
|
||||
# Peuplement du template jinja
|
||||
return HTMLBuilder(
|
||||
header,
|
||||
_mini_timeline(),
|
||||
@ -731,11 +727,12 @@ def visu_assiduites_group():
|
||||
Returns:
|
||||
str: l'html généré
|
||||
"""
|
||||
|
||||
# Récupération des paramètres de la requête
|
||||
formsemestre_id: int = request.args.get("formsemestre_id", -1)
|
||||
moduleimpl_id: int = request.args.get("moduleimpl_id")
|
||||
date: str = request.args.get("jour", datetime.date.today().isoformat())
|
||||
group_ids: list[int] = request.args.get("group_ids", None)
|
||||
|
||||
if group_ids is None:
|
||||
group_ids = []
|
||||
else:
|
||||
@ -755,6 +752,7 @@ def visu_assiduites_group():
|
||||
except (TypeError, ValueError) as exc:
|
||||
raise ScoValueError("identifiant de formsemestre invalide") from exc
|
||||
|
||||
# Récupérations des/du groupe(s)
|
||||
groups_infos = sco_groups_view.DisplayedGroupsInfos(
|
||||
group_ids, moduleimpl_id=moduleimpl_id, formsemestre_id=formsemestre_id
|
||||
)
|
||||
@ -765,10 +763,6 @@ def visu_assiduites_group():
|
||||
+ html_sco_header.sco_footer()
|
||||
)
|
||||
|
||||
# --- URL DEFAULT ---
|
||||
|
||||
base_url: str = f"SignalAssiduiteGr?date={date}&{groups_infos.groups_query_args}"
|
||||
|
||||
# --- Filtrage par formsemestre ---
|
||||
formsemestre_id = groups_infos.formsemestre_id
|
||||
|
||||
@ -776,10 +770,10 @@ def visu_assiduites_group():
|
||||
if formsemestre.dept_id != g.scodoc_dept_id:
|
||||
abort(404, "groupes inexistants dans ce département")
|
||||
|
||||
require_module = sco_preferences.get_preference(
|
||||
"abs_require_module", formsemestre_id
|
||||
)
|
||||
# Vérfication du forçage du module
|
||||
require_module = sco_preferences.get_preference("forcer_module", formsemestre_id)
|
||||
|
||||
# Récupération des étudiants du/des groupe(s)
|
||||
etuds = [
|
||||
sco_etud.get_etud_info(etudid=m["etudid"], filled=True)[0]
|
||||
for m in groups_infos.members
|
||||
@ -810,7 +804,6 @@ def visu_assiduites_group():
|
||||
moduleimpl_id = None
|
||||
|
||||
# --- Génération de l'HTML ---
|
||||
sem = formsemestre.to_dict()
|
||||
|
||||
if groups_infos.tous_les_etuds_du_sem:
|
||||
gr_tit = "en"
|
||||
@ -841,6 +834,9 @@ def visu_assiduites_group():
|
||||
],
|
||||
)
|
||||
|
||||
# Récupération du semestre en dictionnaire
|
||||
sem = formsemestre.to_dict()
|
||||
|
||||
return HTMLBuilder(
|
||||
header,
|
||||
_mini_timeline(),
|
||||
@ -873,10 +869,14 @@ def visu_assiduites_group():
|
||||
@permission_required(Permission.ScoView)
|
||||
def etat_abs_date():
|
||||
"""date_debut, date_fin en ISO"""
|
||||
|
||||
# Récupération des paramètre de la requête
|
||||
date_debut_str = request.args.get("date_debut")
|
||||
date_fin_str = request.args.get("date_fin")
|
||||
title = request.args.get("desc")
|
||||
group_ids: list[int] = request.args.get("group_ids", None)
|
||||
|
||||
# Vérification des dates
|
||||
try:
|
||||
date_debut = datetime.datetime.fromisoformat(date_debut_str)
|
||||
except ValueError as exc:
|
||||
@ -885,6 +885,8 @@ def etat_abs_date():
|
||||
date_fin = datetime.datetime.fromisoformat(date_fin_str)
|
||||
except ValueError as exc:
|
||||
raise ScoValueError("date_fin invalide") from exc
|
||||
|
||||
# Vérification des groupes
|
||||
if group_ids is None:
|
||||
group_ids = []
|
||||
else:
|
||||
@ -893,25 +895,30 @@ def etat_abs_date():
|
||||
|
||||
groups_infos = sco_groups_view.DisplayedGroupsInfos(group_ids)
|
||||
|
||||
# Récupération des étudiants des groupes
|
||||
etuds = [
|
||||
sco_etud.get_etud_info(etudid=m["etudid"], filled=True)[0]
|
||||
for m in groups_infos.members
|
||||
]
|
||||
# Récupération des assiduites des étudiants
|
||||
assiduites: Assiduite = Assiduite.query.filter(
|
||||
Assiduite.etudid.in_([e["etudid"] for e in etuds])
|
||||
)
|
||||
# Filtrage des assiduités en fonction des dates données
|
||||
assiduites = scass.filter_by_date(
|
||||
assiduites, Assiduite, date_debut, date_fin, False
|
||||
)
|
||||
|
||||
# Génération d'objet étudiant simplifié (nom+lien cal, etat_assiduite)
|
||||
etudiants: list[dict] = []
|
||||
for etud in etuds:
|
||||
# On récupère l'état de la première assiduité sur la période
|
||||
assi = assiduites.filter_by(etudid=etud["etudid"]).first()
|
||||
|
||||
etat = ""
|
||||
if assi is not None and assi.etat != 0:
|
||||
etat = scu.EtatAssiduite.inverse().get(assi.etat).name
|
||||
|
||||
# On génère l'objet simplifié
|
||||
etudiant = {
|
||||
"nom": f"""<a href="{url_for(
|
||||
"assiduites.calendrier_etud",
|
||||
@ -923,8 +930,10 @@ def etat_abs_date():
|
||||
|
||||
etudiants.append(etudiant)
|
||||
|
||||
# On tri les étudiants
|
||||
etudiants = list(sorted(etudiants, key=lambda x: x["nom"]))
|
||||
|
||||
# Génération de l'HTML
|
||||
header: str = html_sco_header.sco_header(
|
||||
page_title=safehtml.html_to_safe_html(title),
|
||||
init_qtip=True,
|
||||
@ -948,6 +957,8 @@ def etat_abs_date():
|
||||
@permission_required(Permission.ScoView)
|
||||
def visu_assi_group():
|
||||
"""Visualisation de l'assiduité d'un groupe entre deux dates"""
|
||||
|
||||
# Récupération des paramètres de la requête
|
||||
dates = {
|
||||
"debut": request.args.get("date_debut"),
|
||||
"fin": request.args.get("date_fin"),
|
||||
@ -961,13 +972,17 @@ def visu_assi_group():
|
||||
group_ids = group_ids.split(",")
|
||||
map(str, group_ids)
|
||||
|
||||
# Récupération des groupes, du semestre et des étudiants
|
||||
groups_infos = sco_groups_view.DisplayedGroupsInfos(group_ids)
|
||||
formsemestre = db.session.get(FormSemestre, groups_infos.formsemestre_id)
|
||||
etuds = etuds_sorted_from_ids([m["etudid"] for m in groups_infos.members])
|
||||
|
||||
# Génération du tableau des assiduités
|
||||
table: TableAssi = TableAssi(
|
||||
etuds=etuds, dates=list(dates.values()), formsemestre=formsemestre
|
||||
)
|
||||
|
||||
# Export en XLS
|
||||
if fmt.startswith("xls"):
|
||||
return scu.send_file(
|
||||
table.excel(),
|
||||
@ -976,6 +991,7 @@ def visu_assi_group():
|
||||
suffix=scu.XLSX_SUFFIX,
|
||||
)
|
||||
|
||||
# récupération du/des noms du/des groupes
|
||||
if groups_infos.tous_les_etuds_du_sem:
|
||||
gr_tit = ""
|
||||
grp = ""
|
||||
@ -988,6 +1004,7 @@ def visu_assi_group():
|
||||
grp + ' <span class="fontred">' + groups_infos.groups_titles + "</span>"
|
||||
)
|
||||
|
||||
# Génération de la page
|
||||
return render_template(
|
||||
"assiduites/pages/visu_assi.j2",
|
||||
assi_metric=scu.translate_assiduites_metric(
|
||||
@ -1040,26 +1057,27 @@ def test():
|
||||
@scodoc
|
||||
@permission_required(Permission.AbsChange)
|
||||
def signal_assiduites_diff():
|
||||
# Récupération des paramètres de la requête
|
||||
group_ids: list[int] = request.args.get("group_ids", None)
|
||||
formsemestre_id: int = request.args.get("formsemestre_id", -1)
|
||||
date: str = request.args.get("jour", datetime.date.today().isoformat())
|
||||
|
||||
date_deb: str = request.args.get("date_deb")
|
||||
date_fin: str = request.args.get("date_fin")
|
||||
|
||||
semaine: str = request.args.get("semaine")
|
||||
|
||||
# Dans le cas où on donne une semaine plutot qu'un jour
|
||||
if semaine is not None:
|
||||
# On génère la semaine iso à partir de l'anne scolaire.
|
||||
semaine = (
|
||||
f"{scu.annee_scolaire()}-W{semaine}" if "W" not in semaine else semaine
|
||||
)
|
||||
# On met à jour les dates avec le date de debut et fin de semaine
|
||||
date_deb: datetime.date = datetime.datetime.strptime(
|
||||
semaine + "-1", "%Y-W%W-%w"
|
||||
)
|
||||
date_fin: datetime.date = date_deb + datetime.timedelta(days=6)
|
||||
|
||||
etudiants: list[dict] = []
|
||||
|
||||
titre = None
|
||||
|
||||
# Vérification du formsemestre_id
|
||||
@ -1079,14 +1097,13 @@ def signal_assiduites_diff():
|
||||
elif real_date > formsemestre.date_fin:
|
||||
date = formsemestre.date_fin.isoformat()
|
||||
|
||||
# Vérification des groupes
|
||||
if group_ids is None:
|
||||
group_ids = []
|
||||
else:
|
||||
group_ids = group_ids.split(",")
|
||||
map(str, group_ids)
|
||||
|
||||
groups_infos = sco_groups_view.DisplayedGroupsInfos(group_ids)
|
||||
|
||||
if not groups_infos.members:
|
||||
return (
|
||||
html_sco_header.sco_header(page_title="Assiduité: saisie différée")
|
||||
@ -1094,6 +1111,7 @@ def signal_assiduites_diff():
|
||||
+ html_sco_header.sco_footer()
|
||||
)
|
||||
|
||||
# Récupération des étudiants
|
||||
etudiants.extend(
|
||||
[
|
||||
sco_etud.get_etud_info(etudid=m["etudid"], filled=True)[0]
|
||||
@ -1103,6 +1121,8 @@ def signal_assiduites_diff():
|
||||
|
||||
etudiants = list(sorted(etudiants, key=lambda x: x["nom"]))
|
||||
|
||||
# Génération de l'HTML
|
||||
|
||||
header: str = html_sco_header.sco_header(
|
||||
page_title="Assiduité: saisie différée",
|
||||
init_qtip=True,
|
||||
@ -1169,13 +1189,17 @@ def signal_evaluation_abs(etudid: int = None, evaluation_id: int = None):
|
||||
Alors l'absence sera sur la période de l'évaluation
|
||||
Sinon L'utilisateur sera redirigé vers la page de saisie des absences de l'étudiant
|
||||
"""
|
||||
|
||||
# Récupération de l'étudiant concerné
|
||||
etud: Identite = Identite.query.get_or_404(etudid)
|
||||
if etud.dept_id != g.scodoc_dept_id:
|
||||
abort(404, "étudiant inexistant dans ce département")
|
||||
|
||||
# Récupération de l'évaluation concernée
|
||||
evaluation: Evaluation = Evaluation.query.get_or_404(evaluation_id)
|
||||
|
||||
delta: datetime.timedelta = evaluation.date_fin - evaluation.date_debut
|
||||
# Si l'évaluation dure plus qu'un jour alors on redirige vers la page de saisie etudiant
|
||||
if delta > datetime.timedelta(days=1):
|
||||
# rediriger vers page saisie
|
||||
return redirect(
|
||||
@ -1191,7 +1215,7 @@ def signal_evaluation_abs(etudid: int = None, evaluation_id: int = None):
|
||||
)
|
||||
)
|
||||
|
||||
# créer l'assiduité
|
||||
# Sinon on créé l'assiduité
|
||||
|
||||
try:
|
||||
assiduite_unique: Assiduite = Assiduite.create_assiduite(
|
||||
@ -1202,8 +1226,8 @@ def signal_evaluation_abs(etudid: int = None, evaluation_id: int = None):
|
||||
moduleimpl=evaluation.moduleimpl,
|
||||
)
|
||||
except ScoValueError as see:
|
||||
# En cas d'erreur
|
||||
msg: str = see.args[0]
|
||||
|
||||
if "Duplication" in msg:
|
||||
msg = "Une autre assiduité concerne déjà cette période. En cliquant sur continuer vous serez redirigé vers la page de saisie des assiduités de l'étudiant."
|
||||
dest: str = url_for(
|
||||
@ -1222,6 +1246,7 @@ def signal_evaluation_abs(etudid: int = None, evaluation_id: int = None):
|
||||
db.session.add(assiduite_unique)
|
||||
db.session.commit()
|
||||
|
||||
# on flash pour indiquer que l'absence a bien été créée puis on revient sur la page de l'évaluation
|
||||
flash("L'absence a bien été créée")
|
||||
# rediriger vers la page d'évaluation
|
||||
return redirect(
|
||||
@ -1235,16 +1260,20 @@ def signal_evaluation_abs(etudid: int = None, evaluation_id: int = None):
|
||||
|
||||
def generate_bul_list(etud: Identite, semestre: FormSemestre) -> str:
|
||||
"""Génère la liste des assiduités d'un étudiant pour le bulletin mail"""
|
||||
|
||||
# On récupère la métrique d'assiduité
|
||||
metrique: str = scu.translate_assiduites_metric(
|
||||
sco_preferences.get_preference("assi_metrique", formsemestre_id=semestre.id),
|
||||
)
|
||||
|
||||
# On récupère le nombre maximum de ligne d'assiduité
|
||||
max_nb: int = int(
|
||||
sco_preferences.get_preference(
|
||||
"bul_mail_list_abs_nb", formsemestre_id=semestre.id
|
||||
)
|
||||
)
|
||||
|
||||
# On récupère les assiduités et les justificatifs de l'étudiant
|
||||
assiduites = scass.filter_by_formsemestre(
|
||||
etud.assiduites, Assiduite, semestre
|
||||
).order_by(Assiduite.entry_date.desc())
|
||||
@ -1252,10 +1281,17 @@ def generate_bul_list(etud: Identite, semestre: FormSemestre) -> str:
|
||||
etud.justificatifs, Justificatif, semestre
|
||||
).order_by(Justificatif.entry_date.desc())
|
||||
|
||||
# On calcule les statistiques
|
||||
stats: dict = scass.get_assiduites_stats(
|
||||
assiduites, metric=metrique, filtered={"split": True}
|
||||
)
|
||||
|
||||
# On sépare :
|
||||
# - abs_j = absences justifiées
|
||||
# - abs_nj = absences non justifiées
|
||||
# - retards = les retards
|
||||
# - justifs = les justificatifs
|
||||
|
||||
abs_j: list[str] = [
|
||||
{"date": _get_date_str(assi.date_debut, assi.date_fin)}
|
||||
for assi in assiduites
|
||||
@ -1302,13 +1338,36 @@ def generate_bul_list(etud: Identite, semestre: FormSemestre) -> str:
|
||||
|
||||
|
||||
def _get_date_str(deb: datetime.datetime, fin: datetime.datetime) -> str:
|
||||
"""
|
||||
_get_date_str transforme une période en chaîne lisible
|
||||
|
||||
Args:
|
||||
deb (datetime.datetime): date de début
|
||||
fin (datetime.datetime): date de fin
|
||||
|
||||
Returns:
|
||||
str:
|
||||
"le dd/mm/yyyy de hh:MM à hh:MM" si les deux date sont sur le même jour
|
||||
"du dd/mm/yyyy hh:MM audd/mm/yyyy hh:MM" sinon
|
||||
"""
|
||||
if deb.date() == fin.date():
|
||||
temps = deb.strftime("%d/%m/%Y %H:%M").split(" ") + [fin.strftime("%H:%M")]
|
||||
return f"le {temps[0]} de {temps[1]} à {temps[2]}"
|
||||
return f'du {deb.strftime("%d/%m/%Y %H:%M")} au {fin.strftime("%d/%m/%Y %H:%M")}'
|
||||
|
||||
|
||||
def _get_days_between_dates(deb: str, fin: str):
|
||||
def _get_days_between_dates(deb: str, fin: str) -> str:
|
||||
"""
|
||||
_get_days_between_dates récupère tous les jours entre deux dates
|
||||
|
||||
Args:
|
||||
deb (str): date de début
|
||||
fin (str): date de fin
|
||||
|
||||
Returns:
|
||||
str: une chaine json représentant une liste des jours
|
||||
['date_iso','date_iso2', ...]
|
||||
"""
|
||||
if deb is None or fin is None:
|
||||
return "null"
|
||||
try:
|
||||
@ -1328,8 +1387,25 @@ def _get_days_between_dates(deb: str, fin: str):
|
||||
|
||||
|
||||
def _differee(
|
||||
etudiants, moduleimpl_select, date=None, periode=None, formsemestre_id=None
|
||||
):
|
||||
etudiants: list[dict],
|
||||
moduleimpl_select: str,
|
||||
date: str = None,
|
||||
periode: dict[str, str] = None,
|
||||
formsemestre_id: int = None,
|
||||
) -> str:
|
||||
"""
|
||||
_differee Génère un tableau de saisie différé
|
||||
|
||||
Args:
|
||||
etudiants (list[dict]): la liste des étudiants (représentés par des dictionnaires)
|
||||
moduleimpl_select (str): l'html représentant le selecteur de module
|
||||
date (str, optional): la première date à afficher. Defaults to None.
|
||||
periode (dict[str, str], optional):La période par défaut de la première colonne. Defaults to None.
|
||||
formsemestre_id (int, optional): l'id du semestre pour le selecteur de module. Defaults to None.
|
||||
|
||||
Returns:
|
||||
str: le widget (html/css/js)
|
||||
"""
|
||||
if date is None:
|
||||
date = datetime.date.today().isoformat()
|
||||
|
||||
@ -1356,9 +1432,7 @@ def _differee(
|
||||
)
|
||||
|
||||
|
||||
def _module_selector(
|
||||
formsemestre: FormSemestre, moduleimpl_id: int = None
|
||||
) -> HTMLElement:
|
||||
def _module_selector(formsemestre: FormSemestre, moduleimpl_id: int = None) -> str:
|
||||
"""
|
||||
_module_selector Génère un HTMLSelectElement à partir des moduleimpl du formsemestre
|
||||
|
||||
@ -1368,18 +1442,26 @@ def _module_selector(
|
||||
Returns:
|
||||
str: La représentation str d'un HTMLSelectElement
|
||||
"""
|
||||
|
||||
# récupération des ues du semestre
|
||||
ntc: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
|
||||
ues = ntc.get_ues_stat_dict()
|
||||
|
||||
modimpls_list: list[dict] = []
|
||||
ues = ntc.get_ues_stat_dict()
|
||||
for ue in ues:
|
||||
# Ajout des moduleimpl de chaque ue dans la liste des moduleimpls
|
||||
modimpls_list += ntc.get_modimpls_dict(ue_id=ue["ue_id"])
|
||||
|
||||
# prévoie la sélection par défaut d'un moduleimpl s'il a été passé en paramètre
|
||||
selected = "" if moduleimpl_id is not None else "selected"
|
||||
|
||||
modules = []
|
||||
# Vérification que le moduleimpl_id passé en paramètre est bien un entier
|
||||
try:
|
||||
moduleimpl_id = int(moduleimpl_id)
|
||||
except (ValueError, TypeError):
|
||||
moduleimpl_id = None
|
||||
|
||||
modules: list[dict[str, str | int]] = []
|
||||
# Récupération de l'id et d'un nom lisible pour chaque moduleimpl
|
||||
for modimpl in modimpls_list:
|
||||
modname: str = (
|
||||
(modimpl["module"]["code"] or "")
|
||||
@ -1388,11 +1470,6 @@ def _module_selector(
|
||||
)
|
||||
modules.append({"moduleimpl_id": modimpl["moduleimpl_id"], "name": modname})
|
||||
|
||||
try:
|
||||
moduleimpl_id = int(moduleimpl_id)
|
||||
except (ValueError, TypeError):
|
||||
moduleimpl_id = None
|
||||
|
||||
return render_template(
|
||||
"assiduites/widgets/moduleimpl_selector.j2",
|
||||
selected=selected,
|
||||
@ -1401,13 +1478,30 @@ def _module_selector(
|
||||
)
|
||||
|
||||
|
||||
def _dynamic_module_selector():
|
||||
def _dynamic_module_selector() -> str:
|
||||
"""
|
||||
_dynamic_module_selector retourne l'html/css/javascript du selecteur de module dynamique
|
||||
|
||||
Returns:
|
||||
str: l'html/css/javascript du selecteur de module dynamique
|
||||
"""
|
||||
return render_template(
|
||||
"assiduites/widgets/moduleimpl_dynamic_selector.j2",
|
||||
)
|
||||
|
||||
|
||||
def _timeline(formsemestre_id=None) -> HTMLElement:
|
||||
def _timeline(formsemestre_id: int = None) -> str:
|
||||
"""
|
||||
_timeline retourne l'html de la timeline
|
||||
|
||||
Args:
|
||||
formsemestre_id (int, optional): un formsemestre. Defaults to None.
|
||||
Le formsemestre sert à obtenir la période par défaut de la timeline
|
||||
sinon ce sera de 2 heure dès le début de la timeline
|
||||
|
||||
Returns:
|
||||
str: l'html en chaîne de caractères
|
||||
"""
|
||||
return render_template(
|
||||
"assiduites/widgets/timeline.j2",
|
||||
t_start=ScoDocSiteConfig.assi_get_rounded_time("assi_morning_time", "08:00:00"),
|
||||
@ -1419,7 +1513,13 @@ def _timeline(formsemestre_id=None) -> HTMLElement:
|
||||
)
|
||||
|
||||
|
||||
def _mini_timeline() -> HTMLElement:
|
||||
def _mini_timeline() -> str:
|
||||
"""
|
||||
_mini_timeline Retourne l'html lié au mini timeline d'assiduités
|
||||
|
||||
Returns:
|
||||
str: l'html en chaîne de caractères
|
||||
"""
|
||||
return render_template(
|
||||
"assiduites/widgets/minitimeline.j2",
|
||||
t_start=ScoDocSiteConfig.assi_get_rounded_time("assi_morning_time", "08:00:00"),
|
||||
|
@ -1,7 +1,7 @@
|
||||
# -*- mode: python -*-
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
SCOVERSION = "9.6.49"
|
||||
SCOVERSION = "9.6.50"
|
||||
|
||||
SCONAME = "ScoDoc"
|
||||
|
||||
|
@ -26,6 +26,7 @@ def downgrade_module(
|
||||
dept_etudid: list[int] = None
|
||||
dept_id: int = None
|
||||
|
||||
# Récupération du département si spécifié
|
||||
if dept is not None:
|
||||
departement: Departement = Departement.query.filter_by(acronym=dept).first()
|
||||
|
||||
@ -34,13 +35,16 @@ def downgrade_module(
|
||||
dept_etudid = [etud.id for etud in departement.etudiants]
|
||||
dept_id = departement.id
|
||||
|
||||
# Suppression des assiduités
|
||||
if assiduites:
|
||||
_remove_assiduites(dept_etudid)
|
||||
|
||||
# Suppression des justificatifs
|
||||
if justificatifs:
|
||||
_remove_justificatifs(dept_etudid)
|
||||
_remove_justificatifs_archive(dept_id)
|
||||
|
||||
# Si on supprime tout le module assiduité/justificatif alors on remet à zero
|
||||
# les séquences postgres
|
||||
if dept is None:
|
||||
if assiduites:
|
||||
db.session.execute(
|
||||
@ -51,26 +55,52 @@ def downgrade_module(
|
||||
sa.text("ALTER SEQUENCE justificatifs_id_seq RESTART WITH 1")
|
||||
)
|
||||
|
||||
# On valide l'opération sur la bdd
|
||||
db.session.commit()
|
||||
|
||||
# On affiche un message pour l'utilisateur
|
||||
print(
|
||||
f"{TerminalColor.GREEN}Le module assiduité a bien été remis à zero.{TerminalColor.RESET}"
|
||||
)
|
||||
|
||||
|
||||
def _remove_assiduites(dept_etudid: str = None):
|
||||
"""
|
||||
_remove_assiduites Supprime les assiduités
|
||||
|
||||
Args:
|
||||
dept_etudid (str, optional): la liste des etudid d'un département. Defaults to None.
|
||||
"""
|
||||
if dept_etudid is None:
|
||||
# Si pas d'étudids alors on supprime toutes les assiduités
|
||||
Assiduite.query.delete()
|
||||
else:
|
||||
# Sinon on supprime que les assiduités des étudiants donnés
|
||||
Assiduite.query.filter(Assiduite.etudid.in_(dept_etudid)).delete()
|
||||
|
||||
|
||||
def _remove_justificatifs(dept_etudid: str = None):
|
||||
"""
|
||||
_remove_justificatifs Supprime les justificatifs
|
||||
|
||||
Args:
|
||||
dept_etudid (str, optional): la liste des etudid d'un département. Defaults to None.
|
||||
"""
|
||||
if dept_etudid is None:
|
||||
# Si pas d'étudids alors on supprime tous les justificatifs
|
||||
Justificatif.query.delete()
|
||||
else:
|
||||
# Sinon on supprime que les justificatifs des étudiants donnés
|
||||
Justificatif.query.filter(Justificatif.etudid.in_(dept_etudid)).delete()
|
||||
|
||||
|
||||
def _remove_justificatifs_archive(dept_id: int = None):
|
||||
"""
|
||||
_remove_justificatifs_archive On supprime les archives des fichiers justificatifs
|
||||
|
||||
Args:
|
||||
dept_id (int, optional): l'id du département à supprimer . Defaults to None.
|
||||
Si none : supprime tous les département
|
||||
Sinon uniquement le département sélectionné
|
||||
"""
|
||||
JustificatifArchiver().remove_dept_archive(dept_id)
|
||||
|
Loading…
Reference in New Issue
Block a user