forked from ScoDoc/ScoDoc
Merge pull request 'Remise en place API Assiduites/evaluations et ajout Permission JustifValidate' (#945) from iziram/ScoDoc:permission_justif into master
Reviewed-on: ScoDoc/ScoDoc#945
This commit is contained in:
commit
c2a248633f
@ -12,6 +12,7 @@ from flask_json import as_json
|
|||||||
from flask_login import current_user, login_required
|
from flask_login import current_user, login_required
|
||||||
from flask_sqlalchemy.query import Query
|
from flask_sqlalchemy.query import Query
|
||||||
from sqlalchemy.orm.exc import ObjectDeletedError
|
from sqlalchemy.orm.exc import ObjectDeletedError
|
||||||
|
from werkzeug.exceptions import HTTPException
|
||||||
|
|
||||||
from app import db, log, set_sco_dept
|
from app import db, log, set_sco_dept
|
||||||
import app.scodoc.sco_assiduites as scass
|
import app.scodoc.sco_assiduites as scass
|
||||||
@ -21,6 +22,7 @@ from app.api import api_web_bp, get_model_api_object, tools
|
|||||||
from app.decorators import permission_required, scodoc
|
from app.decorators import permission_required, scodoc
|
||||||
from app.models import (
|
from app.models import (
|
||||||
Assiduite,
|
Assiduite,
|
||||||
|
Evaluation,
|
||||||
FormSemestre,
|
FormSemestre,
|
||||||
Identite,
|
Identite,
|
||||||
ModuleImpl,
|
ModuleImpl,
|
||||||
@ -282,6 +284,7 @@ def assiduites(etudid: int = None, nip=None, ine=None, with_query: bool = False)
|
|||||||
404,
|
404,
|
||||||
message="étudiant inconnu",
|
message="étudiant inconnu",
|
||||||
)
|
)
|
||||||
|
|
||||||
# Récupération des assiduités de l'étudiant
|
# Récupération des assiduités de l'étudiant
|
||||||
assiduites_query: Query = etud.assiduites
|
assiduites_query: Query = etud.assiduites
|
||||||
|
|
||||||
@ -304,6 +307,92 @@ def assiduites(etudid: int = None, nip=None, ine=None, with_query: bool = False)
|
|||||||
return data_set
|
return data_set
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route("/assiduites/<int:etudid>/evaluations")
|
||||||
|
@api_web_bp.route("/assiduites/<int:etudid>/evaluations")
|
||||||
|
# etudid
|
||||||
|
@bp.route("/assiduites/etudid/<int:etudid>/evaluations")
|
||||||
|
@api_web_bp.route("/assiduites/etudid/<int:etudid>/evaluations")
|
||||||
|
# ine
|
||||||
|
@bp.route("/assiduites/ine/<ine>/evaluations")
|
||||||
|
@api_web_bp.route("/assiduites/ine/<ine>/evaluations")
|
||||||
|
# nip
|
||||||
|
@bp.route("/assiduites/nip/<nip>/evaluations")
|
||||||
|
@api_web_bp.route("/assiduites/nip/<nip>/evaluations")
|
||||||
|
@login_required
|
||||||
|
@scodoc
|
||||||
|
@as_json
|
||||||
|
@permission_required(Permission.ScoView)
|
||||||
|
def assiduites_evaluations(etudid: int = None, nip=None, ine=None):
|
||||||
|
"""
|
||||||
|
Retourne la liste de toutes les évaluations de l'étudiant
|
||||||
|
Pour chaque évaluation, retourne la liste des objets assiduités
|
||||||
|
sur la plage de l'évaluation
|
||||||
|
|
||||||
|
Présentation du retour :
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"evaluation_id": 1234,
|
||||||
|
"assiduites": [
|
||||||
|
{
|
||||||
|
"assiduite_id":1234,
|
||||||
|
...
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
"""
|
||||||
|
# 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",
|
||||||
|
)
|
||||||
|
|
||||||
|
# Récupération des évaluations et des assidiutés
|
||||||
|
etud_evaluations_assiduites: list[dict] = scass.get_etud_evaluations_assiduites(
|
||||||
|
etud
|
||||||
|
)
|
||||||
|
|
||||||
|
return etud_evaluations_assiduites
|
||||||
|
|
||||||
|
|
||||||
|
@api_web_bp.route("/evaluation/<int:evaluation_id>/assiduites")
|
||||||
|
@bp.route("/evaluation/<int:evaluation_id>/assiduites")
|
||||||
|
@login_required
|
||||||
|
@scodoc
|
||||||
|
@as_json
|
||||||
|
@permission_required(Permission.ScoView)
|
||||||
|
def evaluation_assiduites(evaluation_id):
|
||||||
|
"""
|
||||||
|
Retourne les objets assiduités de chaque étudiant sur la plage de l'évaluation
|
||||||
|
Présentation du retour :
|
||||||
|
{
|
||||||
|
"<etudid>" : [
|
||||||
|
{
|
||||||
|
"assiduite_id":1234,
|
||||||
|
...
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
# Récupération de l'évaluation
|
||||||
|
try:
|
||||||
|
evaluation: Evaluation = Evaluation.get_evaluation(evaluation_id)
|
||||||
|
except HTTPException:
|
||||||
|
return json_error(404, "L'évaluation n'existe pas")
|
||||||
|
|
||||||
|
evaluation_assiduites_par_etudid: dict[int, list[Assiduite]] = {}
|
||||||
|
for assi in scass.get_evaluation_assiduites(evaluation):
|
||||||
|
etudid: str = str(assi.etudid)
|
||||||
|
etud_assiduites = evaluation_assiduites_par_etudid.get(etudid, [])
|
||||||
|
etud_assiduites.append(assi.to_dict(format_api=True))
|
||||||
|
evaluation_assiduites_par_etudid[etudid] = etud_assiduites
|
||||||
|
|
||||||
|
return evaluation_assiduites_par_etudid
|
||||||
|
|
||||||
|
|
||||||
@bp.route("/assiduites/group/query", defaults={"with_query": True})
|
@bp.route("/assiduites/group/query", defaults={"with_query": True})
|
||||||
@api_web_bp.route("/assiduites/group/query", defaults={"with_query": True})
|
@api_web_bp.route("/assiduites/group/query", defaults={"with_query": True})
|
||||||
@login_required
|
@login_required
|
||||||
|
@ -374,6 +374,10 @@ def _create_one(
|
|||||||
errors.append("param 'etat': invalide")
|
errors.append("param 'etat': invalide")
|
||||||
|
|
||||||
etat: scu.EtatJustificatif = scu.EtatJustificatif.get(etat)
|
etat: scu.EtatJustificatif = scu.EtatJustificatif.get(etat)
|
||||||
|
if etat != scu.EtatJustificatif.ATTENTE and not current_user.has_permission(
|
||||||
|
Permission.JustifValidate
|
||||||
|
):
|
||||||
|
errors.append("param 'etat': non autorisé (Permission.JustifValidate)")
|
||||||
|
|
||||||
# cas 2 : date_debut
|
# cas 2 : date_debut
|
||||||
date_debut: str = data.get("date_debut", None)
|
date_debut: str = data.get("date_debut", None)
|
||||||
@ -473,7 +477,10 @@ def justif_edit(justif_id: int):
|
|||||||
if etat is None:
|
if etat is None:
|
||||||
errors.append("param 'etat': invalide")
|
errors.append("param 'etat': invalide")
|
||||||
else:
|
else:
|
||||||
|
if current_user.has_permission(Permission.JustifValidate):
|
||||||
justificatif_unique.etat = etat
|
justificatif_unique.etat = etat
|
||||||
|
else:
|
||||||
|
errors.append("param 'etat': non autorisé (Permission.JustifValidate)")
|
||||||
|
|
||||||
# Cas 2 : raison
|
# Cas 2 : raison
|
||||||
raison: str = data.get("raison", False)
|
raison: str = data.get("raison", False)
|
||||||
|
@ -170,13 +170,7 @@ class AjoutJustificatifEtudForm(AjoutAssiOrJustForm):
|
|||||||
)
|
)
|
||||||
etat = SelectField(
|
etat = SelectField(
|
||||||
"État du justificatif",
|
"État du justificatif",
|
||||||
choices=[
|
choices=[], # sera rempli dynamiquement
|
||||||
("", "Choisir..."), # Placeholder
|
|
||||||
(scu.EtatJustificatif.ATTENTE.value, "En attente de validation"),
|
|
||||||
(scu.EtatJustificatif.NON_VALIDE.value, "Non valide"),
|
|
||||||
(scu.EtatJustificatif.MODIFIE.value, "Modifié"),
|
|
||||||
(scu.EtatJustificatif.VALIDE.value, "Valide"),
|
|
||||||
],
|
|
||||||
validators=[DataRequired(message="This field is required.")],
|
validators=[DataRequired(message="This field is required.")],
|
||||||
)
|
)
|
||||||
fichiers = MultipleFileField(label="Ajouter des fichiers")
|
fichiers = MultipleFileField(label="Ajouter des fichiers")
|
||||||
|
@ -10,6 +10,7 @@ from flask_sqlalchemy.query import Query
|
|||||||
|
|
||||||
from app import log, db, set_sco_dept
|
from app import log, db, set_sco_dept
|
||||||
from app.models import (
|
from app.models import (
|
||||||
|
Evaluation,
|
||||||
Identite,
|
Identite,
|
||||||
FormSemestre,
|
FormSemestre,
|
||||||
FormSemestreInscription,
|
FormSemestreInscription,
|
||||||
@ -731,6 +732,93 @@ def create_absence_billet(
|
|||||||
return calculator.to_dict()["demi"]
|
return calculator.to_dict()["demi"]
|
||||||
|
|
||||||
|
|
||||||
|
def get_evaluation_assiduites(evaluation: Evaluation) -> Query:
|
||||||
|
"""
|
||||||
|
Renvoie une query d'assiduité en fonction des étudiants inscrits à l'évaluation
|
||||||
|
et de la date de l'évaluation.
|
||||||
|
|
||||||
|
Attention : Si l'évaluation n'a pas de date, renvoie une liste vide
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Evaluation sans date
|
||||||
|
if evaluation.date_debut is None:
|
||||||
|
return []
|
||||||
|
|
||||||
|
# Récupération des étudiants inscrits à l'évaluation
|
||||||
|
etuds: Query = Identite.query.join(
|
||||||
|
ModuleImplInscription, Identite.id == ModuleImplInscription.etudid
|
||||||
|
).filter(ModuleImplInscription.moduleimpl_id == evaluation.moduleimpl_id)
|
||||||
|
|
||||||
|
etudids: list[int] = [etud.id for etud in etuds]
|
||||||
|
|
||||||
|
# Récupération des assiduités des étudiants inscrits à l'évaluation
|
||||||
|
date_debut: datetime = evaluation.date_debut
|
||||||
|
date_fin: datetime
|
||||||
|
|
||||||
|
if evaluation.date_fin is not None:
|
||||||
|
date_fin = evaluation.date_fin
|
||||||
|
else:
|
||||||
|
# On met à la fin de la journée de date_debut
|
||||||
|
date_fin = datetime.combine(date_debut.date(), time.max)
|
||||||
|
|
||||||
|
# Filtrage par rapport à la plage de l'évaluation
|
||||||
|
assiduites: Query = Assiduite.query.filter(
|
||||||
|
Assiduite.date_debut >= date_debut,
|
||||||
|
Assiduite.date_fin <= date_fin,
|
||||||
|
Assiduite.etudid.in_(etudids),
|
||||||
|
)
|
||||||
|
|
||||||
|
return assiduites
|
||||||
|
|
||||||
|
|
||||||
|
def get_etud_evaluations_assiduites(etud: Identite) -> list[dict]:
|
||||||
|
"""
|
||||||
|
Retourne la liste des évaluations d'un étudiant. Pour chaque évaluation,
|
||||||
|
retourne la liste des assiduités concernant la plage de l'évaluation.
|
||||||
|
"""
|
||||||
|
|
||||||
|
etud_evaluations_assiduites: list[dict] = []
|
||||||
|
|
||||||
|
# On récupère les moduleimpls puis les évaluations liés aux moduleimpls
|
||||||
|
modsimpl_ids: list[int] = [
|
||||||
|
modimp_inscr.moduleimpl_id
|
||||||
|
for modimp_inscr in ModuleImplInscription.query.filter_by(etudid=etud.id)
|
||||||
|
]
|
||||||
|
evaluations: Query = Evaluation.query.filter(
|
||||||
|
Evaluation.moduleimpl_id.in_(modsimpl_ids)
|
||||||
|
)
|
||||||
|
# Pour chaque évaluation, on récupère l'assiduité de l'étudiant sur la plage
|
||||||
|
# de l'évaluation
|
||||||
|
|
||||||
|
for evaluation in evaluations:
|
||||||
|
eval_assis: dict = {"evaluation_id": evaluation.id, "assiduites": []}
|
||||||
|
# Pas d'assiduités si pas de date
|
||||||
|
if evaluation.date_debut is not None:
|
||||||
|
date_debut: datetime = evaluation.date_debut
|
||||||
|
date_fin: datetime
|
||||||
|
|
||||||
|
if evaluation.date_fin is not None:
|
||||||
|
date_fin = evaluation.date_fin
|
||||||
|
else:
|
||||||
|
# On met à la fin de la journée de date_debut
|
||||||
|
date_fin = datetime.combine(date_debut.date(), time.max)
|
||||||
|
|
||||||
|
# Filtrage par rapport à la plage de l'évaluation
|
||||||
|
assiduites: Query = etud.assiduites.filter(
|
||||||
|
Assiduite.date_debut >= date_debut,
|
||||||
|
Assiduite.date_fin <= date_fin,
|
||||||
|
)
|
||||||
|
# On récupère les assiduités et on met à jour le dictionnaire
|
||||||
|
eval_assis["assiduites"] = [
|
||||||
|
assi.to_dict(format_api=True) for assi in assiduites
|
||||||
|
]
|
||||||
|
|
||||||
|
# On ajoute le dictionnaire à la liste des évaluations
|
||||||
|
etud_evaluations_assiduites.append(eval_assis)
|
||||||
|
|
||||||
|
return etud_evaluations_assiduites
|
||||||
|
|
||||||
|
|
||||||
# Gestion du cache
|
# Gestion du cache
|
||||||
def get_assiduites_count(etudid: int, sem: dict) -> tuple[int, int, int]:
|
def get_assiduites_count(etudid: int, sem: dict) -> tuple[int, int, int]:
|
||||||
"""Les comptes d'absences de cet étudiant dans ce semestre:
|
"""Les comptes d'absences de cet étudiant dans ce semestre:
|
||||||
|
@ -1136,7 +1136,9 @@ def formsemestre_status(formsemestre_id=None, check_parcours=True):
|
|||||||
|
|
||||||
# --- Lien Traitement Justificatifs:
|
# --- Lien Traitement Justificatifs:
|
||||||
|
|
||||||
if current_user.has_permission(Permission.AbsJustifView):
|
if current_user.has_permission(
|
||||||
|
Permission.AbsJustifView
|
||||||
|
) and current_user.has_permission(Permission.JustifValidate):
|
||||||
H.append(
|
H.append(
|
||||||
f"""<p>
|
f"""<p>
|
||||||
<a class="stdlink" href="{url_for('assiduites.traitement_justificatifs',
|
<a class="stdlink" href="{url_for('assiduites.traitement_justificatifs',
|
||||||
|
@ -68,6 +68,11 @@ _SCO_PERMISSIONS = (
|
|||||||
"AbsJustifView",
|
"AbsJustifView",
|
||||||
"Visualisation du détail des justificatifs (motif, fichiers)",
|
"Visualisation du détail des justificatifs (motif, fichiers)",
|
||||||
),
|
),
|
||||||
|
(
|
||||||
|
1 << 51,
|
||||||
|
"JustifValidate",
|
||||||
|
"Définir la validité d'un justificatif (valide, invalide, modifié)",
|
||||||
|
),
|
||||||
# Attention: les permissions sont codées sur 64 bits.
|
# Attention: les permissions sont codées sur 64 bits.
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -519,11 +519,22 @@ def _record_assiduite_etud(
|
|||||||
|
|
||||||
if assi_etat != scu.EtatAssiduite.PRESENT and form.est_just.data:
|
if assi_etat != scu.EtatAssiduite.PRESENT and form.est_just.data:
|
||||||
# si la case "justifiée est cochée alors on créé un justificatif de même période"
|
# si la case "justifiée est cochée alors on créé un justificatif de même période"
|
||||||
|
|
||||||
|
# L'état est Valide si l'user à la permission JustifValidate
|
||||||
|
etat: scu.EtatJustificatif = scu.EtatJustificatif.ATTENTE
|
||||||
|
if current_user.has_permission(Permission.JustifValidate):
|
||||||
|
etat = scu.EtatJustificatif.VALIDE
|
||||||
|
else:
|
||||||
|
flash(
|
||||||
|
"Vous ne pouvez pas créer de justificatif valide,"
|
||||||
|
+ " il est automatiquement passé 'EN ATTENTE'",
|
||||||
|
)
|
||||||
|
|
||||||
justi: Justificatif = Justificatif.create_justificatif(
|
justi: Justificatif = Justificatif.create_justificatif(
|
||||||
etudiant=etud,
|
etudiant=etud,
|
||||||
date_debut=dt_debut_tz_server,
|
date_debut=dt_debut_tz_server,
|
||||||
date_fin=dt_fin_tz_server,
|
date_fin=dt_fin_tz_server,
|
||||||
etat=scu.EtatJustificatif.VALIDE,
|
etat=etat,
|
||||||
user_id=current_user.id,
|
user_id=current_user.id,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -642,6 +653,21 @@ def edit_justificatif_etud(justif_id: int):
|
|||||||
readonly = not current_user.has_permission(Permission.AbsChange)
|
readonly = not current_user.has_permission(Permission.AbsChange)
|
||||||
|
|
||||||
form = AjoutJustificatifEtudForm(obj=justif)
|
form = AjoutJustificatifEtudForm(obj=justif)
|
||||||
|
# Limite les choix d'état si l'utilisateur n'a pas la permission de valider
|
||||||
|
choix_etat: list = [
|
||||||
|
(scu.EtatJustificatif.ATTENTE.value, "En attente de validation")
|
||||||
|
]
|
||||||
|
|
||||||
|
if current_user.has_permission(Permission.JustifValidate):
|
||||||
|
choix_etat = [
|
||||||
|
("", "Choisir..."),
|
||||||
|
(scu.EtatJustificatif.ATTENTE.value, "En attente de validation"),
|
||||||
|
(scu.EtatJustificatif.NON_VALIDE.value, "Non valide"),
|
||||||
|
(scu.EtatJustificatif.MODIFIE.value, "Modifié"),
|
||||||
|
(scu.EtatJustificatif.VALIDE.value, "Valide"),
|
||||||
|
]
|
||||||
|
|
||||||
|
form.etat.choices = choix_etat
|
||||||
|
|
||||||
if readonly:
|
if readonly:
|
||||||
form.disable_all()
|
form.disable_all()
|
||||||
@ -717,6 +743,21 @@ def ajout_justificatif_etud():
|
|||||||
)
|
)
|
||||||
|
|
||||||
form = AjoutJustificatifEtudForm()
|
form = AjoutJustificatifEtudForm()
|
||||||
|
# Limite les choix d'état si l'utilisateur n'a pas la permission de valider
|
||||||
|
choix_etat: list = [
|
||||||
|
(scu.EtatJustificatif.ATTENTE.value, "En attente de validation")
|
||||||
|
]
|
||||||
|
|
||||||
|
if current_user.has_permission(Permission.JustifValidate):
|
||||||
|
choix_etat = [
|
||||||
|
("", "Choisir..."),
|
||||||
|
(scu.EtatJustificatif.ATTENTE.value, "En attente de validation"),
|
||||||
|
(scu.EtatJustificatif.NON_VALIDE.value, "Non valide"),
|
||||||
|
(scu.EtatJustificatif.MODIFIE.value, "Modifié"),
|
||||||
|
(scu.EtatJustificatif.VALIDE.value, "Valide"),
|
||||||
|
]
|
||||||
|
|
||||||
|
form.etat.choices = choix_etat
|
||||||
if form.validate_on_submit():
|
if form.validate_on_submit():
|
||||||
if form.cancel.data: # cancel button
|
if form.cancel.data: # cancel button
|
||||||
return redirect(redirect_url)
|
return redirect(redirect_url)
|
||||||
@ -782,6 +823,16 @@ def _record_justificatif_etud(
|
|||||||
form.set_error("Erreur: état invalide")
|
form.set_error("Erreur: état invalide")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
if (
|
||||||
|
not current_user.has_permission(Permission.JustifValidate)
|
||||||
|
and etat != scu.EtatJustificatif.ATTENTE
|
||||||
|
):
|
||||||
|
log("_record_justificatif_etud: pas la permission")
|
||||||
|
form.set_error(
|
||||||
|
"Erreur: vous n'avez pas la permission de définir la validité d'un justificatif"
|
||||||
|
)
|
||||||
|
return False
|
||||||
|
|
||||||
try:
|
try:
|
||||||
message = ""
|
message = ""
|
||||||
if justif:
|
if justif:
|
||||||
@ -1521,12 +1572,22 @@ def tableau_assiduite_actions():
|
|||||||
|
|
||||||
# Justification d'une assiduité depuis le tableau
|
# Justification d'une assiduité depuis le tableau
|
||||||
if action == "justifier" and obj_type == "assiduite":
|
if action == "justifier" and obj_type == "assiduite":
|
||||||
|
# L'état est Valide si l'user à la permission JustifValidate
|
||||||
|
etat: scu.EtatJustificatif = scu.EtatJustificatif.ATTENTE
|
||||||
|
if current_user.has_permission(Permission.JustifValidate):
|
||||||
|
etat = scu.EtatJustificatif.VALIDE
|
||||||
|
else:
|
||||||
|
flash(
|
||||||
|
"Vous ne pouvez pas créer de justificatif valide,"
|
||||||
|
+ " il est automatiquement passé 'EN ATTENTE'",
|
||||||
|
)
|
||||||
|
|
||||||
# Création du justificatif correspondant
|
# Création du justificatif correspondant
|
||||||
justificatif_correspondant: Justificatif = Justificatif.create_justificatif(
|
justificatif_correspondant: Justificatif = Justificatif.create_justificatif(
|
||||||
etudiant=objet.etudiant,
|
etudiant=objet.etudiant,
|
||||||
date_debut=objet.date_debut,
|
date_debut=objet.date_debut,
|
||||||
date_fin=objet.date_fin,
|
date_fin=objet.date_fin,
|
||||||
etat=scu.EtatJustificatif.VALIDE,
|
etat=etat,
|
||||||
user_id=current_user.id,
|
user_id=current_user.id,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -1534,6 +1595,7 @@ def tableau_assiduite_actions():
|
|||||||
scass.simple_invalidate_cache(
|
scass.simple_invalidate_cache(
|
||||||
justificatif_correspondant.to_dict(), objet.etudiant.id
|
justificatif_correspondant.to_dict(), objet.etudiant.id
|
||||||
)
|
)
|
||||||
|
if etat == scu.EtatJustificatif.VALIDE:
|
||||||
flash(f"{objet_name} justifiée")
|
flash(f"{objet_name} justifiée")
|
||||||
return redirect(request.referrer)
|
return redirect(request.referrer)
|
||||||
|
|
||||||
@ -1695,6 +1757,7 @@ def signale_evaluation_abs(etudid: int = None, evaluation_id: int = None):
|
|||||||
|
|
||||||
@bp.route("traitement_justificatifs")
|
@bp.route("traitement_justificatifs")
|
||||||
@scodoc
|
@scodoc
|
||||||
|
@permission_required(Permission.JustifValidate)
|
||||||
@permission_required(Permission.AbsJustifView)
|
@permission_required(Permission.AbsJustifView)
|
||||||
def traitement_justificatifs():
|
def traitement_justificatifs():
|
||||||
"""Page de traitement des justificatifs
|
"""Page de traitement des justificatifs
|
||||||
|
36
migrations/versions/60119446aab6_.py
Normal file
36
migrations/versions/60119446aab6_.py
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
"""justif_validate_permission
|
||||||
|
|
||||||
|
Revision ID: 60119446aab6
|
||||||
|
Revises: 07f37334727b
|
||||||
|
Create Date: 2024-06-26 16:20:12.283501
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision = "60119446aab6"
|
||||||
|
down_revision = "07f37334727b"
|
||||||
|
branch_labels = None
|
||||||
|
depends_on = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade():
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
# ### end Alembic commands ###
|
||||||
|
# Modification des permissions API
|
||||||
|
# AbsChange (1 << 13) = 8192
|
||||||
|
# JustifValidate (1 << 51 ) = 2251799813685248
|
||||||
|
op.execute(
|
||||||
|
"""
|
||||||
|
update role set permissions = permissions | 2251799813685248 where (permissions & 8192) <> 0;
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade():
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
# ### end Alembic commands ###
|
||||||
|
pass
|
@ -43,6 +43,8 @@ ASSIDUITES_FIELDS = {
|
|||||||
"external_data": dict,
|
"external_data": dict,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ASSIDUITES_EVALUATIONS_FIELDS = {"evaluation_id": int, "assiduites": list}
|
||||||
|
|
||||||
CREATE_FIELD = {"assiduite_id": int}
|
CREATE_FIELD = {"assiduite_id": int}
|
||||||
BATCH_FIELD = {"errors": list, "success": list}
|
BATCH_FIELD = {"errors": list, "success": list}
|
||||||
|
|
||||||
@ -139,6 +141,46 @@ def test_route_assiduites(api_headers):
|
|||||||
check_failure_get(f"/assiduites/{FAUX}/query?", api_headers)
|
check_failure_get(f"/assiduites/{FAUX}/query?", api_headers)
|
||||||
|
|
||||||
|
|
||||||
|
def test_route_assiduites_evaluations(api_headers):
|
||||||
|
"""test de la route /assiduites/<etudid:int>/evaluations"""
|
||||||
|
|
||||||
|
# Bon fonctionnement
|
||||||
|
|
||||||
|
data = GET(
|
||||||
|
path=f"/assiduites/{ETUDID}/evaluations", headers=api_headers, dept=DEPT_ACRONYM
|
||||||
|
)
|
||||||
|
assert isinstance(data, list)
|
||||||
|
for evals in data:
|
||||||
|
check_fields(evals, ASSIDUITES_EVALUATIONS_FIELDS)
|
||||||
|
for assi in evals["assiduites"]:
|
||||||
|
check_fields(assi, ASSIDUITES_FIELDS)
|
||||||
|
|
||||||
|
# Mauvais fonctionnement
|
||||||
|
check_failure_get(f"/assiduites/{FAUX}/evaluations", api_headers)
|
||||||
|
|
||||||
|
|
||||||
|
def test_route_evaluations_assiduites(api_headers):
|
||||||
|
"""test de la route /evaluation/<int:evaluation_id>/assiduites"""
|
||||||
|
|
||||||
|
# Bon fonctionnement
|
||||||
|
evaluation_id = 1
|
||||||
|
data = GET(
|
||||||
|
path=f"/evaluation/{evaluation_id}/assiduites",
|
||||||
|
headers=api_headers,
|
||||||
|
dept=DEPT_ACRONYM,
|
||||||
|
)
|
||||||
|
assert isinstance(data, dict)
|
||||||
|
for key, val in data.items():
|
||||||
|
assert isinstance(key, str), "Erreur les clés ne sont pas des strings"
|
||||||
|
assert isinstance(val, list), "Erreur, les valeurs ne sont pas des listes"
|
||||||
|
|
||||||
|
for assi in val:
|
||||||
|
check_fields(assi, ASSIDUITES_FIELDS)
|
||||||
|
|
||||||
|
# Mauvais fonctionnement
|
||||||
|
check_failure_get(f"/evaluation/{FAUX}/assiduites", api_headers)
|
||||||
|
|
||||||
|
|
||||||
def test_route_formsemestre_assiduites(api_headers):
|
def test_route_formsemestre_assiduites(api_headers):
|
||||||
"""test de la route /assiduites/formsemestre/<formsemestre_id:int>"""
|
"""test de la route /assiduites/formsemestre/<formsemestre_id:int>"""
|
||||||
|
|
||||||
|
@ -7,13 +7,17 @@ ses fonctions liées
|
|||||||
|
|
||||||
Ecrit par HARTMANN Matthias (en s'inspirant de tests.unit.test_abs_count.py par Fares Amer )
|
Ecrit par HARTMANN Matthias (en s'inspirant de tests.unit.test_abs_count.py par Fares Amer )
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
from flask_sqlalchemy.query import Query
|
||||||
|
|
||||||
|
|
||||||
import app.scodoc.sco_assiduites as scass
|
import app.scodoc.sco_assiduites as scass
|
||||||
import app.scodoc.sco_utils as scu
|
import app.scodoc.sco_utils as scu
|
||||||
from app import db, log
|
from app import db, log
|
||||||
from app.models import (
|
from app.models import (
|
||||||
Assiduite,
|
Assiduite,
|
||||||
|
Evaluation,
|
||||||
FormSemestre,
|
FormSemestre,
|
||||||
Identite,
|
Identite,
|
||||||
Justificatif,
|
Justificatif,
|
||||||
@ -1115,6 +1119,7 @@ def _setup_fake_db(
|
|||||||
"formsemestres": formsemestres,
|
"formsemestres": formsemestres,
|
||||||
"etuds": etuds,
|
"etuds": etuds,
|
||||||
"etud_faux": etud_faux,
|
"etud_faux": etud_faux,
|
||||||
|
"g_fake": g_fake,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -1730,3 +1735,104 @@ def test_cache_assiduites(test_client):
|
|||||||
) == (1, 1, 2)
|
) == (1, 1, 2)
|
||||||
# Deuxième semestre 2nj / 1j / 3t (Identique car cache et non modifié)
|
# Deuxième semestre 2nj / 1j / 3t (Identique car cache et non modifié)
|
||||||
assert scass.get_assiduites_count(etud.id, formsemestre2.to_dict()) == (2, 1, 3)
|
assert scass.get_assiduites_count(etud.id, formsemestre2.to_dict()) == (2, 1, 3)
|
||||||
|
|
||||||
|
|
||||||
|
def test_recuperation_evaluations(test_client):
|
||||||
|
"""
|
||||||
|
Vérification du bon fonctionnement de la récupération des assiduités d'une évaluation
|
||||||
|
et de la récupération de l'assiduité aux évaluations d'un étudiant
|
||||||
|
"""
|
||||||
|
|
||||||
|
data = _setup_fake_db(
|
||||||
|
[("2024-01-01", "2024-06-30")],
|
||||||
|
1,
|
||||||
|
1,
|
||||||
|
)
|
||||||
|
moduleimpl: ModuleImpl = data["moduleimpls"][0]
|
||||||
|
etud: Identite = data["etuds"][0]
|
||||||
|
|
||||||
|
# Création d'assiduités pour tester les évaluations
|
||||||
|
assiduites_dates = [
|
||||||
|
"2024-01-01",
|
||||||
|
"2024-01-02",
|
||||||
|
"2024-01-03",
|
||||||
|
]
|
||||||
|
assiduites = []
|
||||||
|
for assi in assiduites_dates:
|
||||||
|
assiduites.append(
|
||||||
|
Assiduite.create_assiduite(
|
||||||
|
etud=etud,
|
||||||
|
date_debut=scu.is_iso_formated(assi + "T10:00", True),
|
||||||
|
date_fin=scu.is_iso_formated(assi + "T12:00", True),
|
||||||
|
etat=scu.EtatAssiduite.ABSENT,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
# On génère une évaluation sans date
|
||||||
|
# elle devrait renvoyer une liste vide
|
||||||
|
evaluation_1: Evaluation = Evaluation.create(
|
||||||
|
moduleimpl=moduleimpl,
|
||||||
|
)
|
||||||
|
assert scass.get_evaluation_assiduites(evaluation_1) == []
|
||||||
|
|
||||||
|
# On génère une évaluation le 01/01/24 de 10h à 12h
|
||||||
|
evaluation_2: Evaluation = Evaluation.create(
|
||||||
|
moduleimpl=moduleimpl,
|
||||||
|
date_debut=scu.is_iso_formated("2024-01-01T10:00", True),
|
||||||
|
date_fin=scu.is_iso_formated("2024-01-01T12:00", True),
|
||||||
|
)
|
||||||
|
query: Query = scass.get_evaluation_assiduites(evaluation_2)
|
||||||
|
assert isinstance(query, Query), "Erreur, la fonction ne renvoie pas une Query"
|
||||||
|
|
||||||
|
# On vérifie le contenu de la query
|
||||||
|
# Cette query devrait contenir que la première assiduité
|
||||||
|
assert (
|
||||||
|
query.count() == 1
|
||||||
|
), "Erreur, la query ne contient pas le bon nombre d'assiduités"
|
||||||
|
assert (
|
||||||
|
query.first() == assiduites[0]
|
||||||
|
), "Erreur, la query ne contient pas la bonne assiduité"
|
||||||
|
|
||||||
|
# On génère une évaluation du 02/01/24 au 03/01/24
|
||||||
|
evaluation_3: Evaluation = Evaluation.create(
|
||||||
|
moduleimpl=moduleimpl,
|
||||||
|
date_debut=scu.is_iso_formated("2024-01-02T10:00", True),
|
||||||
|
date_fin=scu.is_iso_formated("2024-01-03T12:00", True),
|
||||||
|
)
|
||||||
|
|
||||||
|
query: Query = scass.get_evaluation_assiduites(evaluation_3)
|
||||||
|
assert isinstance(query, Query), "Erreur, la fonction ne renvoie pas une Query"
|
||||||
|
|
||||||
|
# On vérifie le contenu de la query
|
||||||
|
# On devrait avoir les deux dernières assiduités
|
||||||
|
assert (
|
||||||
|
query.count() == 2
|
||||||
|
), "Erreur, la query ne contient pas le bon nombre d'assiduités"
|
||||||
|
assert (
|
||||||
|
query.all() == assiduites[1:]
|
||||||
|
), "Erreur, la query ne contient pas les bonnes assiduités"
|
||||||
|
|
||||||
|
# Test de la récupération des assiduités aux évaluations
|
||||||
|
evaluations_assiduites = scass.get_etud_evaluations_assiduites(etud)
|
||||||
|
|
||||||
|
assert isinstance(
|
||||||
|
evaluations_assiduites, list
|
||||||
|
), "Erreur, la fonction ne renvoie pas une liste"
|
||||||
|
assert len(evaluations_assiduites) == 3, "Erreur, le nombre d'évaluations est faux"
|
||||||
|
assert all(
|
||||||
|
isinstance(e, dict) for e in evaluations_assiduites
|
||||||
|
), "Erreur, les éléments de la liste ne sont pas des dictionnaires"
|
||||||
|
assert all(
|
||||||
|
"evaluation_id" in e and "assiduites" in e for e in evaluations_assiduites
|
||||||
|
), "Erreur, les dictionnaires ne contiennent pas les bonnes clés"
|
||||||
|
assert (
|
||||||
|
evaluations_assiduites[0]["assiduites"] == []
|
||||||
|
), "Erreur, la première évaluation ne devrait pas contenir d'assiduités"
|
||||||
|
|
||||||
|
assert evaluations_assiduites[1]["assiduites"][0] == assiduites[0].to_dict(
|
||||||
|
format_api=True
|
||||||
|
), "Erreur, la deuxième évaluation n'est pas bonne"
|
||||||
|
|
||||||
|
assert evaluations_assiduites[2]["assiduites"] == [
|
||||||
|
assi.to_dict(format_api=True) for assi in assiduites[1:]
|
||||||
|
], "Erreur, la troisième évaluation n'est pas bonne"
|
||||||
|
@ -395,12 +395,13 @@ def ajouter_assiduites_justificatifs(formsemestre: FormSemestre):
|
|||||||
MODS.append(None)
|
MODS.append(None)
|
||||||
|
|
||||||
for etud in formsemestre.etuds:
|
for etud in formsemestre.etuds:
|
||||||
|
# Se base sur la date des évaluations
|
||||||
base_date = datetime.datetime(
|
base_date = datetime.datetime(
|
||||||
2021, 9, [6, 13, 20, 27][random.randint(0, 3)], 8, 0, 0
|
2022, 3, [1, 8, 15, 22, 29][random.randint(0, 4)], 8, 0, 0
|
||||||
)
|
)
|
||||||
base_date = localize_datetime(base_date)
|
base_date = localize_datetime(base_date)
|
||||||
|
|
||||||
for i in range(random.randint(1, 5)):
|
for i in range(random.randint(1, 4)):
|
||||||
etat = random.randint(0, 2)
|
etat = random.randint(0, 2)
|
||||||
moduleimpl = random.choice(MODS)
|
moduleimpl = random.choice(MODS)
|
||||||
deb_date = base_date + datetime.timedelta(days=i)
|
deb_date = base_date + datetime.timedelta(days=i)
|
||||||
|
Loading…
Reference in New Issue
Block a user