Remise en état api/assiduites/evaluations

prend en compte le fix du test api
git revert du revert
This reverts commit ddfd94b0ba.
This commit is contained in:
Iziram 2024-06-26 11:20:28 +02:00
parent 48376724f5
commit b7b5d08de0
5 changed files with 328 additions and 2 deletions

View File

@ -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

View File

@ -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:

View File

@ -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>"""

View File

@ -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"

View File

@ -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)