forked from ScoDoc/ScoDoc
module assiduité: corrections linter
This commit is contained in:
parent
84ac334b13
commit
5aeffcbf1d
@ -182,6 +182,7 @@ def assiduites(etudid: int = None, with_query: bool = False):
|
|||||||
@scodoc
|
@scodoc
|
||||||
@permission_required(Permission.ScoView)
|
@permission_required(Permission.ScoView)
|
||||||
def assiduites_formsemestre(formsemestre_id: int, with_query: bool = False):
|
def assiduites_formsemestre(formsemestre_id: int, with_query: bool = False):
|
||||||
|
"""Retourne toutes les assiduités du formsemestre"""
|
||||||
formsemestre: FormSemestre = None
|
formsemestre: FormSemestre = None
|
||||||
formsemestre_id = int(formsemestre_id)
|
formsemestre_id = int(formsemestre_id)
|
||||||
formsemestre = FormSemestre.query.filter_by(id=formsemestre_id).first()
|
formsemestre = FormSemestre.query.filter_by(id=formsemestre_id).first()
|
||||||
@ -224,6 +225,7 @@ def assiduites_formsemestre(formsemestre_id: int, with_query: bool = False):
|
|||||||
def count_assiduites_formsemestre(
|
def count_assiduites_formsemestre(
|
||||||
formsemestre_id: int = None, with_query: bool = False
|
formsemestre_id: int = None, with_query: bool = False
|
||||||
):
|
):
|
||||||
|
"""Comptage des assiduités du formsemestre"""
|
||||||
formsemestre: FormSemestre = None
|
formsemestre: FormSemestre = None
|
||||||
formsemestre_id = int(formsemestre_id)
|
formsemestre_id = int(formsemestre_id)
|
||||||
formsemestre = FormSemestre.query.filter_by(id=formsemestre_id).first()
|
formsemestre = FormSemestre.query.filter_by(id=formsemestre_id).first()
|
||||||
|
@ -288,7 +288,7 @@ def justif_edit(justif_id: int):
|
|||||||
etuid=justificatif_unique.etudid
|
etuid=justificatif_unique.etudid
|
||||||
).all()
|
).all()
|
||||||
|
|
||||||
if is_period_conflicting(deb, fin, justificatifs_list):
|
if is_period_conflicting(deb, fin, justificatifs_list, Justificatif):
|
||||||
errors.append(
|
errors.append(
|
||||||
"Modification de la plage horaire impossible: conflit avec les autres justificatifs"
|
"Modification de la plage horaire impossible: conflit avec les autres justificatifs"
|
||||||
)
|
)
|
||||||
@ -357,8 +357,8 @@ def _delete_singular(justif_id: int, database):
|
|||||||
|
|
||||||
|
|
||||||
# Partie archivage
|
# Partie archivage
|
||||||
@bp.route("/justificatif/import/<int:justif_id>", methods=["POST"])
|
@bp.route("/justificatif/<int:justif_id>/import", methods=["POST"])
|
||||||
@api_web_bp.route("/justificatif/import/<int:justif_id>", methods=["POST"])
|
@api_web_bp.route("/justificatif/<int:justif_id>/import", methods=["POST"])
|
||||||
@scodoc
|
@scodoc
|
||||||
@login_required
|
@login_required
|
||||||
@permission_required(Permission.ScoView)
|
@permission_required(Permission.ScoView)
|
||||||
@ -402,8 +402,8 @@ def justif_import(justif_id: int = None):
|
|||||||
return json_error(404, err.args[0])
|
return json_error(404, err.args[0])
|
||||||
|
|
||||||
|
|
||||||
@bp.route("/justificatif/export/<int:justif_id>/<filename>", methods=["POST"])
|
@bp.route("/justificatif/<int:justif_id>/export/<filename>", methods=["POST"])
|
||||||
@api_web_bp.route("/justificatif/export/<int:justif_id>/<filename>", methods=["POST"])
|
@api_web_bp.route("/justificatif/<int:justif_id>/export/<filename>", methods=["POST"])
|
||||||
@scodoc
|
@scodoc
|
||||||
@login_required
|
@login_required
|
||||||
@permission_required(Permission.ScoView)
|
@permission_required(Permission.ScoView)
|
||||||
@ -433,8 +433,8 @@ def justif_export(justif_id: int = None, filename: str = None):
|
|||||||
return json_error(404, err.args[0])
|
return json_error(404, err.args[0])
|
||||||
|
|
||||||
|
|
||||||
@bp.route("/justificatif/remove/<int:justif_id>", methods=["POST"])
|
@bp.route("/justificatif/<int:justif_id>/remove", methods=["POST"])
|
||||||
@api_web_bp.route("/justificatif/remove/<int:justif_id>", methods=["POST"])
|
@api_web_bp.route("/justificatif/<int:justif_id>/remove", methods=["POST"])
|
||||||
@scodoc
|
@scodoc
|
||||||
@login_required
|
@login_required
|
||||||
@permission_required(Permission.ScoView)
|
@permission_required(Permission.ScoView)
|
||||||
@ -497,8 +497,8 @@ def justif_remove(justif_id: int = None):
|
|||||||
return jsonify({"response": "removed"})
|
return jsonify({"response": "removed"})
|
||||||
|
|
||||||
|
|
||||||
@bp.route("/justificatif/list/<int:justif_id>", methods=["GET"])
|
@bp.route("/justificatif/<int:justif_id>/list", methods=["GET"])
|
||||||
@api_web_bp.route("/justificatif/list/<int:justif_id>", methods=["GET"])
|
@api_web_bp.route("/justificatif/<int:justif_id>/list", methods=["GET"])
|
||||||
@scodoc
|
@scodoc
|
||||||
@login_required
|
@login_required
|
||||||
@permission_required(Permission.ScoView)
|
@permission_required(Permission.ScoView)
|
||||||
@ -528,13 +528,13 @@ def justif_list(justif_id: int = None):
|
|||||||
|
|
||||||
|
|
||||||
# Partie justification
|
# Partie justification
|
||||||
@bp.route("/justificatif/justified/<int:justif_id>", methods=["GET"])
|
@bp.route("/justificatif/<int:justif_id>/justifies", methods=["GET"])
|
||||||
@api_web_bp.route("/justificatif/justified/<int:justif_id>", methods=["GET"])
|
@api_web_bp.route("/justificatif/<int:justif_id>/justifies", methods=["GET"])
|
||||||
@scodoc
|
@scodoc
|
||||||
@login_required
|
@login_required
|
||||||
@permission_required(Permission.ScoView)
|
@permission_required(Permission.ScoView)
|
||||||
# @permission_required(Permission.ScoAssiduiteChange)
|
# @permission_required(Permission.ScoAssiduiteChange)
|
||||||
def justif_justified(justif_id: int = None):
|
def justif_justifies(justif_id: int = None):
|
||||||
"""
|
"""
|
||||||
Liste assiduite_id justifiées par le justificatif
|
Liste assiduite_id justifiées par le justificatif
|
||||||
"""
|
"""
|
||||||
|
@ -10,7 +10,6 @@ from app.scodoc.sco_exceptions import ScoValueError
|
|||||||
from app.scodoc.sco_utils import (
|
from app.scodoc.sco_utils import (
|
||||||
EtatAssiduite,
|
EtatAssiduite,
|
||||||
EtatJustificatif,
|
EtatJustificatif,
|
||||||
is_period_overlapping,
|
|
||||||
localize_datetime,
|
localize_datetime,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -52,6 +51,7 @@ class Assiduite(db.Model):
|
|||||||
entry_date = db.Column(db.DateTime(timezone=True), server_default=db.func.now())
|
entry_date = db.Column(db.DateTime(timezone=True), server_default=db.func.now())
|
||||||
|
|
||||||
def to_dict(self, format_api=True) -> dict:
|
def to_dict(self, format_api=True) -> dict:
|
||||||
|
"""Retourne la représentation json de l'assiduité"""
|
||||||
etat = self.etat
|
etat = self.etat
|
||||||
|
|
||||||
if format_api:
|
if format_api:
|
||||||
|
@ -1,9 +1,14 @@
|
|||||||
|
"""
|
||||||
|
Gestion de l'archivage des justificatifs
|
||||||
|
|
||||||
|
Ecrit par Matthias HARTMANN
|
||||||
|
"""
|
||||||
|
import os
|
||||||
|
from shutil import rmtree
|
||||||
|
|
||||||
|
from app.models import Identite
|
||||||
from app.scodoc.sco_archives import BaseArchiver
|
from app.scodoc.sco_archives import BaseArchiver
|
||||||
from app.scodoc.sco_exceptions import ScoValueError
|
from app.scodoc.sco_exceptions import ScoValueError
|
||||||
from app.models import Identite, Departement
|
|
||||||
|
|
||||||
from shutil import rmtree
|
|
||||||
import os
|
|
||||||
|
|
||||||
|
|
||||||
class JustificatifArchiver(BaseArchiver):
|
class JustificatifArchiver(BaseArchiver):
|
||||||
@ -109,6 +114,9 @@ class JustificatifArchiver(BaseArchiver):
|
|||||||
self.set_dept_id(etud.dept_id)
|
self.set_dept_id(etud.dept_id)
|
||||||
|
|
||||||
def remove_dept_archive(self, dept_id: int = None):
|
def remove_dept_archive(self, dept_id: int = None):
|
||||||
|
"""
|
||||||
|
Supprime toutes les archives d'un département (ou de tous les départements)
|
||||||
|
"""
|
||||||
self.set_dept_id(1)
|
self.set_dept_id(1)
|
||||||
self.initialize()
|
self.initialize()
|
||||||
|
|
||||||
|
@ -1,13 +1,21 @@
|
|||||||
|
"""
|
||||||
|
|
||||||
|
Ensembles des fonctions utilisant les Assiduités et/ou Justificatifs
|
||||||
|
|
||||||
|
Ecrit par Matthias Hartmann.
|
||||||
|
|
||||||
|
"""
|
||||||
from datetime import date, datetime, time, timedelta
|
from datetime import date, datetime, time, timedelta
|
||||||
|
|
||||||
import app.scodoc.sco_utils as scu
|
import app.scodoc.sco_utils as scu
|
||||||
from app.models.assiduites import Assiduite, Justificatif
|
from app.models.assiduites import Assiduite, Justificatif
|
||||||
from app.models.etudiants import Identite
|
from app.models.etudiants import Identite
|
||||||
from app.models.formsemestre import FormSemestre, FormSemestreInscription
|
from app.models.formsemestre import FormSemestre, FormSemestreInscription
|
||||||
from app.profiler import Profiler
|
|
||||||
|
|
||||||
|
|
||||||
class CountCalculator:
|
class CountCalculator:
|
||||||
|
"""Classe qui gére le comptage des assiduités"""
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
morning: time = time(8, 0),
|
morning: time = time(8, 0),
|
||||||
@ -39,21 +47,27 @@ class CountCalculator:
|
|||||||
self.count: int = 0
|
self.count: int = 0
|
||||||
|
|
||||||
def reset(self):
|
def reset(self):
|
||||||
|
"""Remet à zero le compteur"""
|
||||||
self.days = []
|
self.days = []
|
||||||
self.half_days = []
|
self.half_days = []
|
||||||
self.hours = 0.0
|
self.hours = 0.0
|
||||||
self.count = 0
|
self.count = 0
|
||||||
|
|
||||||
def add_half_day(self, day: date, is_morning: bool = True):
|
def add_half_day(self, day: date, is_morning: bool = True):
|
||||||
|
"""Ajoute une demi journée dans le comptage"""
|
||||||
key: tuple[date, bool] = (day, is_morning)
|
key: tuple[date, bool] = (day, is_morning)
|
||||||
if key not in self.half_days:
|
if key not in self.half_days:
|
||||||
self.half_days.append(key)
|
self.half_days.append(key)
|
||||||
|
|
||||||
def add_day(self, day: date):
|
def add_day(self, day: date):
|
||||||
|
"""Ajoute un jour dans le comptage"""
|
||||||
if day not in self.days:
|
if day not in self.days:
|
||||||
self.days.append(day)
|
self.days.append(day)
|
||||||
|
|
||||||
def check_in_morning(self, period: tuple[datetime, datetime]) -> bool:
|
def check_in_morning(self, period: tuple[datetime, datetime]) -> bool:
|
||||||
|
"""Vérifiée si la période donnée fait partie du matin
|
||||||
|
(Test sur la date de début)
|
||||||
|
"""
|
||||||
|
|
||||||
interval_morning: tuple[datetime, datetime] = (
|
interval_morning: tuple[datetime, datetime] = (
|
||||||
scu.localize_datetime(datetime.combine(period[0].date(), self.morning)),
|
scu.localize_datetime(datetime.combine(period[0].date(), self.morning)),
|
||||||
@ -66,6 +80,9 @@ class CountCalculator:
|
|||||||
return in_morning
|
return in_morning
|
||||||
|
|
||||||
def check_in_evening(self, period: tuple[datetime, datetime]) -> bool:
|
def check_in_evening(self, period: tuple[datetime, datetime]) -> bool:
|
||||||
|
"""Vérifie si la période fait partie de l'aprèm
|
||||||
|
(test sur la date de début)
|
||||||
|
"""
|
||||||
|
|
||||||
interval_evening: tuple[datetime, datetime] = (
|
interval_evening: tuple[datetime, datetime] = (
|
||||||
scu.localize_datetime(datetime.combine(period[0].date(), self.after_noon)),
|
scu.localize_datetime(datetime.combine(period[0].date(), self.after_noon)),
|
||||||
@ -77,6 +94,7 @@ class CountCalculator:
|
|||||||
return in_evening
|
return in_evening
|
||||||
|
|
||||||
def compute_long_assiduite(self, assi: Assiduite):
|
def compute_long_assiduite(self, assi: Assiduite):
|
||||||
|
"""Calcule les métriques sur une assiduité longue (plus d'un jour)"""
|
||||||
|
|
||||||
pointer_date: date = assi.date_debut.date() + timedelta(days=1)
|
pointer_date: date = assi.date_debut.date() + timedelta(days=1)
|
||||||
start_hours: timedelta = assi.date_debut - scu.localize_datetime(
|
start_hours: timedelta = assi.date_debut - scu.localize_datetime(
|
||||||
@ -121,6 +139,7 @@ class CountCalculator:
|
|||||||
self.hours += self.hour_per_day - (start_hours.total_seconds() / 3600)
|
self.hours += self.hour_per_day - (start_hours.total_seconds() / 3600)
|
||||||
|
|
||||||
def compute_assiduites(self, assiduites: Assiduite):
|
def compute_assiduites(self, assiduites: Assiduite):
|
||||||
|
"""Calcule les métriques pour la collection d'assiduité donnée"""
|
||||||
assi: Assiduite
|
assi: Assiduite
|
||||||
assiduites: list[Assiduite] = (
|
assiduites: list[Assiduite] = (
|
||||||
assiduites.all() if isinstance(assiduites, Assiduite) else assiduites
|
assiduites.all() if isinstance(assiduites, Assiduite) else assiduites
|
||||||
@ -147,6 +166,7 @@ class CountCalculator:
|
|||||||
self.hours += delta.total_seconds() / 3600
|
self.hours += delta.total_seconds() / 3600
|
||||||
|
|
||||||
def to_dict(self) -> dict[str, object]:
|
def to_dict(self) -> dict[str, object]:
|
||||||
|
"""Retourne les métriques sous la forme d'un dictionnaire"""
|
||||||
return {
|
return {
|
||||||
"compte": self.count,
|
"compte": self.count,
|
||||||
"journee": len(self.days),
|
"journee": len(self.days),
|
||||||
@ -158,6 +178,7 @@ class CountCalculator:
|
|||||||
def get_assiduites_stats(
|
def get_assiduites_stats(
|
||||||
assiduites: Assiduite, metric: str = "all", filtered: dict[str, object] = None
|
assiduites: Assiduite, metric: str = "all", filtered: dict[str, object] = None
|
||||||
) -> Assiduite:
|
) -> Assiduite:
|
||||||
|
"""Compte les assiduités en fonction des filtres"""
|
||||||
|
|
||||||
if filtered is not None:
|
if filtered is not None:
|
||||||
deb, fin = None, None
|
deb, fin = None, None
|
||||||
@ -298,6 +319,10 @@ def justifies(justi: Justificatif, obj: bool = False) -> list[int]:
|
|||||||
def get_all_justified(
|
def get_all_justified(
|
||||||
justificatifs: Justificatif, date_deb: datetime = None, date_fin: datetime = None
|
justificatifs: Justificatif, date_deb: datetime = None, date_fin: datetime = None
|
||||||
) -> list[Assiduite]:
|
) -> list[Assiduite]:
|
||||||
|
"""Retourne toutes les assiduités justifiées par les justificatifs donnés"""
|
||||||
|
|
||||||
|
# TODO: Forcer le filtrage des assiduités en fonction d'une période
|
||||||
|
# => Cas d'un justificatif en bordure de période
|
||||||
if date_deb is None:
|
if date_deb is None:
|
||||||
date_deb = datetime.min
|
date_deb = datetime.min
|
||||||
if date_fin is None:
|
if date_fin is None:
|
||||||
|
@ -58,7 +58,7 @@ from flask import flash, url_for, make_response, jsonify
|
|||||||
from werkzeug.http import HTTP_STATUS_CODES
|
from werkzeug.http import HTTP_STATUS_CODES
|
||||||
|
|
||||||
from config import Config
|
from config import Config
|
||||||
from app import log, db
|
from app import log
|
||||||
from app.scodoc.sco_vdi import ApoEtapeVDI
|
from app.scodoc.sco_vdi import ApoEtapeVDI
|
||||||
from app.scodoc.codes_cursus import NOTES_TOLERANCE, CODES_EXPL
|
from app.scodoc.codes_cursus import NOTES_TOLERANCE, CODES_EXPL
|
||||||
from app.scodoc import sco_xml
|
from app.scodoc import sco_xml
|
||||||
@ -91,7 +91,7 @@ ETATS_INSCRIPTION = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def printProgressBar(
|
def print_progress_bar(
|
||||||
iteration,
|
iteration,
|
||||||
total,
|
total,
|
||||||
prefix="",
|
prefix="",
|
||||||
@ -115,28 +115,30 @@ def printProgressBar(
|
|||||||
autosize - Optional : Choisir automatiquement la taille de la barre en fonction du terminal (Bool)
|
autosize - Optional : Choisir automatiquement la taille de la barre en fonction du terminal (Bool)
|
||||||
"""
|
"""
|
||||||
percent = ("{0:." + str(decimals) + "f}").format(100 * (iteration / float(total)))
|
percent = ("{0:." + str(decimals) + "f}").format(100 * (iteration / float(total)))
|
||||||
color = ProgressBarColors.RED
|
color = TerminalColor.RED
|
||||||
if 50 >= float(percent) > 25:
|
if 50 >= float(percent) > 25:
|
||||||
color = ProgressBarColors.MAGENTA
|
color = TerminalColor.MAGENTA
|
||||||
if 75 >= float(percent) > 50:
|
if 75 >= float(percent) > 50:
|
||||||
color = ProgressBarColors.BLUE
|
color = TerminalColor.BLUE
|
||||||
if 90 >= float(percent) > 75:
|
if 90 >= float(percent) > 75:
|
||||||
color = ProgressBarColors.CYAN
|
color = TerminalColor.CYAN
|
||||||
if 100 >= float(percent) > 90:
|
if 100 >= float(percent) > 90:
|
||||||
color = ProgressBarColors.GREEN
|
color = TerminalColor.GREEN
|
||||||
styling = f"{prefix} |{fill}| {percent}% {suffix}"
|
styling = f"{prefix} |{fill}| {percent}% {suffix}"
|
||||||
if autosize:
|
if autosize:
|
||||||
cols, _ = get_terminal_size(fallback=(length, 1))
|
cols, _ = get_terminal_size(fallback=(length, 1))
|
||||||
length = cols - len(styling)
|
length = cols - len(styling)
|
||||||
filledLength = int(length * iteration // total)
|
filled_length = int(length * iteration // total)
|
||||||
bar = fill * filledLength + "-" * (length - filledLength)
|
pg_bar = fill * filled_length + "-" * (length - filled_length)
|
||||||
print(f"\r{color}{styling.replace(fill, bar)}{ProgressBarColors.RESET}", end="\r")
|
print(f"\r{color}{styling.replace(fill, pg_bar)}{TerminalColor.RESET}", end="\r")
|
||||||
# Affiche une nouvelle ligne vide
|
# Affiche une nouvelle ligne vide
|
||||||
if iteration == total:
|
if iteration == total:
|
||||||
print(f"\n{finish_msg}")
|
print(f"\n{finish_msg}")
|
||||||
|
|
||||||
|
|
||||||
class ProgressBarColors:
|
class TerminalColor:
|
||||||
|
"""Ensemble de couleur pour terminaux"""
|
||||||
|
|
||||||
BLUE = "\033[94m"
|
BLUE = "\033[94m"
|
||||||
CYAN = "\033[96m"
|
CYAN = "\033[96m"
|
||||||
GREEN = "\033[92m"
|
GREEN = "\033[92m"
|
||||||
@ -153,10 +155,12 @@ class BiDirectionalEnum(Enum):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def contains(cls, attr: str):
|
def contains(cls, attr: str):
|
||||||
|
"""Vérifie sur un attribut existe dans l'enum"""
|
||||||
return attr.upper() in cls._member_names_
|
return attr.upper() in cls._member_names_
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get(cls, attr: str, default: any = None):
|
def get(cls, attr: str, default: any = None):
|
||||||
|
"""Récupère une valeur à partir de son attribut"""
|
||||||
val = None
|
val = None
|
||||||
try:
|
try:
|
||||||
val = cls[attr.upper()]
|
val = cls[attr.upper()]
|
||||||
@ -206,11 +210,12 @@ def is_iso_formated(date: str, convert=False) -> bool or datetime.datetime or No
|
|||||||
try:
|
try:
|
||||||
date: datetime.datetime = dtparser.isoparse(date)
|
date: datetime.datetime = dtparser.isoparse(date)
|
||||||
return date if convert else True
|
return date if convert else True
|
||||||
except Exception:
|
except (dtparser.ParserError, ValueError, TypeError):
|
||||||
return None if convert else False
|
return None if convert else False
|
||||||
|
|
||||||
|
|
||||||
def localize_datetime(date: datetime.datetime or str) -> datetime.datetime:
|
def localize_datetime(date: datetime.datetime or str) -> datetime.datetime:
|
||||||
|
"""Ajoute un timecode UTC à la date donnée."""
|
||||||
if isinstance(date, str):
|
if isinstance(date, str):
|
||||||
date = is_iso_formated(date, convert=True)
|
date = is_iso_formated(date, convert=True)
|
||||||
|
|
||||||
|
@ -11,7 +11,7 @@ import sqlalchemy as sa
|
|||||||
|
|
||||||
# revision identifiers, used by Alembic.
|
# revision identifiers, used by Alembic.
|
||||||
revision = "dbcf2175e87f"
|
revision = "dbcf2175e87f"
|
||||||
down_revision = "5c7b208355df"
|
down_revision = "d8288b7f0a3e"
|
||||||
branch_labels = None
|
branch_labels = None
|
||||||
depends_on = None
|
depends_on = None
|
||||||
|
|
||||||
|
@ -34,7 +34,15 @@ COUNT_FIELDS = {"compte": int, "journee": int, "demi": int, "heure": float}
|
|||||||
TO_REMOVE = []
|
TO_REMOVE = []
|
||||||
|
|
||||||
|
|
||||||
def check_fields(data, fields: dict = None):
|
def check_fields(data: dict, fields: dict = None):
|
||||||
|
"""
|
||||||
|
Cette fonction permet de vérifier que le dictionnaire data
|
||||||
|
contient les bonnes clés et les bons types de valeurs.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
data (dict): un dictionnaire (json de retour de l'api)
|
||||||
|
fields (dict, optional): Un dictionnaire représentant les clés et les types d'une réponse.
|
||||||
|
"""
|
||||||
if fields is None:
|
if fields is None:
|
||||||
fields = ASSIDUITES_FIELDS
|
fields = ASSIDUITES_FIELDS
|
||||||
assert set(data.keys()) == set(fields.keys())
|
assert set(data.keys()) == set(fields.keys())
|
||||||
@ -45,7 +53,19 @@ def check_fields(data, fields: dict = None):
|
|||||||
assert isinstance(data[key], fields[key])
|
assert isinstance(data[key], fields[key])
|
||||||
|
|
||||||
|
|
||||||
def check_failure_get(path, headers, err=None):
|
def check_failure_get(path: str, headers: dict, err: str = None):
|
||||||
|
"""
|
||||||
|
Cette fonction vérifiée que la requête GET renvoie bien un 404
|
||||||
|
|
||||||
|
Args:
|
||||||
|
path (str): la route de l'api
|
||||||
|
headers (dict): le token d'auth de l'api
|
||||||
|
err (str, optional): L'erreur qui est sensée être fournie par l'api.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
APIError: Une erreur car la requête a fonctionné (mauvais comportement)
|
||||||
|
"""
|
||||||
|
|
||||||
try:
|
try:
|
||||||
GET(path=path, headers=headers)
|
GET(path=path, headers=headers)
|
||||||
# ^ Renvoi un 404
|
# ^ Renvoi un 404
|
||||||
@ -56,7 +76,20 @@ def check_failure_get(path, headers, err=None):
|
|||||||
raise APIError("Le GET n'aurait pas du fonctionner")
|
raise APIError("Le GET n'aurait pas du fonctionner")
|
||||||
|
|
||||||
|
|
||||||
def check_failure_post(path, headers, data, err=None):
|
def check_failure_post(path: str, headers: dict, data: dict, err: str = None):
|
||||||
|
"""
|
||||||
|
Cette fonction vérifiée que la requête POST renvoie bien un 404
|
||||||
|
|
||||||
|
Args:
|
||||||
|
path (str): la route de l'api
|
||||||
|
headers (dict): le token d'auth
|
||||||
|
data (dict): un dictionnaire (json) à envoyer
|
||||||
|
err (str, optional): L'erreur qui est sensée être fournie par l'api.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
APIError: Une erreur car la requête a fonctionné (mauvais comportement)
|
||||||
|
"""
|
||||||
|
|
||||||
try:
|
try:
|
||||||
data = POST_JSON(path=path, headers=headers, data=data)
|
data = POST_JSON(path=path, headers=headers, data=data)
|
||||||
# ^ Renvoi un 404
|
# ^ Renvoi un 404
|
||||||
@ -68,6 +101,18 @@ def check_failure_post(path, headers, data, err=None):
|
|||||||
|
|
||||||
|
|
||||||
def create_data(etat: str, day: str, module: int = None, desc: str = None):
|
def create_data(etat: str, day: str, module: int = None, desc: str = None):
|
||||||
|
"""
|
||||||
|
Permet de créer un dictionnaire assiduité
|
||||||
|
|
||||||
|
Args:
|
||||||
|
etat (str): l'état de l'assiduité (PRESENT,ABSENT,RETARD)
|
||||||
|
day (str): Le jour de l'assiduité
|
||||||
|
module (int, optional): Le moduleimpl_id associé
|
||||||
|
desc (str, optional): Une description de l'assiduité (eg: motif retard )
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
dict: la représentation d'une assiduité
|
||||||
|
"""
|
||||||
data = {
|
data = {
|
||||||
"date_debut": f"2022-01-{day}T08:00",
|
"date_debut": f"2022-01-{day}T08:00",
|
||||||
"date_fin": f"2022-01-{day}T10:00",
|
"date_fin": f"2022-01-{day}T10:00",
|
||||||
@ -83,6 +128,7 @@ def create_data(etat: str, day: str, module: int = None, desc: str = None):
|
|||||||
|
|
||||||
|
|
||||||
def test_route_assiduite(api_headers):
|
def test_route_assiduite(api_headers):
|
||||||
|
"""test de la route /assiduite/<assiduite_id:int>"""
|
||||||
|
|
||||||
# Bon fonctionnement == id connu
|
# Bon fonctionnement == id connu
|
||||||
data = GET(path="/assiduite/1", headers=api_headers)
|
data = GET(path="/assiduite/1", headers=api_headers)
|
||||||
@ -97,6 +143,7 @@ def test_route_assiduite(api_headers):
|
|||||||
|
|
||||||
|
|
||||||
def test_route_count_assiduites(api_headers):
|
def test_route_count_assiduites(api_headers):
|
||||||
|
"""test de la route /assiduites/<etudid:int>/count"""
|
||||||
|
|
||||||
# Bon fonctionnement
|
# Bon fonctionnement
|
||||||
|
|
||||||
@ -117,6 +164,7 @@ def test_route_count_assiduites(api_headers):
|
|||||||
|
|
||||||
|
|
||||||
def test_route_assiduites(api_headers):
|
def test_route_assiduites(api_headers):
|
||||||
|
"""test de la route /assiduites/<etudid:int>"""
|
||||||
|
|
||||||
# Bon fonctionnement
|
# Bon fonctionnement
|
||||||
|
|
||||||
@ -136,6 +184,7 @@ def test_route_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>"""
|
||||||
|
|
||||||
# Bon fonctionnement
|
# Bon fonctionnement
|
||||||
|
|
||||||
@ -165,6 +214,7 @@ def test_route_formsemestre_assiduites(api_headers):
|
|||||||
|
|
||||||
|
|
||||||
def test_route_count_formsemestre_assiduites(api_headers):
|
def test_route_count_formsemestre_assiduites(api_headers):
|
||||||
|
"""test de la route /assiduites/formsemestre/<formsemestre_id:int>/count"""
|
||||||
|
|
||||||
# Bon fonctionnement
|
# Bon fonctionnement
|
||||||
|
|
||||||
@ -193,6 +243,7 @@ def test_route_count_formsemestre_assiduites(api_headers):
|
|||||||
|
|
||||||
|
|
||||||
def test_route_create(api_headers):
|
def test_route_create(api_headers):
|
||||||
|
"""test de la route /assiduite/<etudid:int>/create"""
|
||||||
|
|
||||||
# -== Unique ==-
|
# -== Unique ==-
|
||||||
|
|
||||||
@ -272,6 +323,7 @@ def test_route_create(api_headers):
|
|||||||
|
|
||||||
|
|
||||||
def test_route_edit(api_headers):
|
def test_route_edit(api_headers):
|
||||||
|
"""test de la route /assiduite/<assiduite_id:int>/edit"""
|
||||||
|
|
||||||
# Bon fonctionnement
|
# Bon fonctionnement
|
||||||
|
|
||||||
@ -296,6 +348,7 @@ def test_route_edit(api_headers):
|
|||||||
|
|
||||||
|
|
||||||
def test_route_delete(api_headers):
|
def test_route_delete(api_headers):
|
||||||
|
"""test de la route /assiduite/delete"""
|
||||||
# -== Unique ==-
|
# -== Unique ==-
|
||||||
|
|
||||||
# Bon fonctionnement
|
# Bon fonctionnement
|
||||||
|
@ -7,15 +7,15 @@ Ecrit par HARTMANN Matthias
|
|||||||
|
|
||||||
from random import randint
|
from random import randint
|
||||||
|
|
||||||
|
import requests
|
||||||
from tests.api.setup_test_api import (
|
from tests.api.setup_test_api import (
|
||||||
|
API_URL,
|
||||||
|
CHECK_CERTIFICATE,
|
||||||
GET,
|
GET,
|
||||||
POST_JSON,
|
POST_JSON,
|
||||||
APIError,
|
APIError,
|
||||||
api_headers,
|
api_headers,
|
||||||
API_URL,
|
|
||||||
CHECK_CERTIFICATE,
|
|
||||||
)
|
)
|
||||||
import requests
|
|
||||||
|
|
||||||
ETUDID = 1
|
ETUDID = 1
|
||||||
FAUX = 42069
|
FAUX = 42069
|
||||||
@ -39,6 +39,14 @@ TO_REMOVE = []
|
|||||||
|
|
||||||
|
|
||||||
def check_fields(data, fields: dict = None):
|
def check_fields(data, fields: dict = None):
|
||||||
|
"""
|
||||||
|
Cette fonction permet de vérifier que le dictionnaire data
|
||||||
|
contient les bonnes clés et les bons types de valeurs.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
data (dict): un dictionnaire (json de retour de l'api)
|
||||||
|
fields (dict, optional): Un dictionnaire représentant les clés et les types d'une réponse.
|
||||||
|
"""
|
||||||
if fields is None:
|
if fields is None:
|
||||||
fields = JUSTIFICATIFS_FIELDS
|
fields = JUSTIFICATIFS_FIELDS
|
||||||
assert set(data.keys()) == set(fields.keys())
|
assert set(data.keys()) == set(fields.keys())
|
||||||
@ -50,6 +58,17 @@ def check_fields(data, fields: dict = None):
|
|||||||
|
|
||||||
|
|
||||||
def check_failure_get(path, headers, err=None):
|
def check_failure_get(path, headers, err=None):
|
||||||
|
"""
|
||||||
|
Cette fonction vérifiée que la requête GET renvoie bien un 404
|
||||||
|
|
||||||
|
Args:
|
||||||
|
path (str): la route de l'api
|
||||||
|
headers (dict): le token d'auth de l'api
|
||||||
|
err (str, optional): L'erreur qui est sensée être fournie par l'api.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
APIError: Une erreur car la requête a fonctionné (mauvais comportement)
|
||||||
|
"""
|
||||||
try:
|
try:
|
||||||
GET(path=path, headers=headers)
|
GET(path=path, headers=headers)
|
||||||
# ^ Renvoi un 404
|
# ^ Renvoi un 404
|
||||||
@ -61,6 +80,18 @@ def check_failure_get(path, headers, err=None):
|
|||||||
|
|
||||||
|
|
||||||
def check_failure_post(path, headers, data, err=None):
|
def check_failure_post(path, headers, data, err=None):
|
||||||
|
"""
|
||||||
|
Cette fonction vérifiée que la requête POST renvoie bien un 404
|
||||||
|
|
||||||
|
Args:
|
||||||
|
path (str): la route de l'api
|
||||||
|
headers (dict): le token d'auth
|
||||||
|
data (dict): un dictionnaire (json) à envoyer
|
||||||
|
err (str, optional): L'erreur qui est sensée être fournie par l'api.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
APIError: Une erreur car la requête a fonctionné (mauvais comportement)
|
||||||
|
"""
|
||||||
try:
|
try:
|
||||||
data = POST_JSON(path=path, headers=headers, data=data)
|
data = POST_JSON(path=path, headers=headers, data=data)
|
||||||
# ^ Renvoi un 404
|
# ^ Renvoi un 404
|
||||||
@ -72,6 +103,17 @@ def check_failure_post(path, headers, data, err=None):
|
|||||||
|
|
||||||
|
|
||||||
def create_data(etat: str, day: str, raison: str = None):
|
def create_data(etat: str, day: str, raison: str = None):
|
||||||
|
"""
|
||||||
|
Permet de créer un dictionnaire assiduité
|
||||||
|
|
||||||
|
Args:
|
||||||
|
etat (str): l'état du justificatif (VALIDE,NON_VALIDE,MODIFIE, ATTENTE)
|
||||||
|
day (str): Le jour du justificatif
|
||||||
|
raison (str, optional): Une description du justificatif (eg: motif retard )
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
dict: la représentation d'une assiduité
|
||||||
|
"""
|
||||||
data = {
|
data = {
|
||||||
"date_debut": f"2022-01-{day}T08:00",
|
"date_debut": f"2022-01-{day}T08:00",
|
||||||
"date_fin": f"2022-01-{day}T10:00",
|
"date_fin": f"2022-01-{day}T10:00",
|
||||||
@ -84,6 +126,7 @@ def create_data(etat: str, day: str, raison: str = None):
|
|||||||
|
|
||||||
|
|
||||||
def test_route_justificatif(api_headers):
|
def test_route_justificatif(api_headers):
|
||||||
|
"""test de la route /justificatif/<justif_id:int>"""
|
||||||
|
|
||||||
# Bon fonctionnement == id connu
|
# Bon fonctionnement == id connu
|
||||||
data = GET(path="/justificatif/1", headers=api_headers)
|
data = GET(path="/justificatif/1", headers=api_headers)
|
||||||
@ -98,7 +141,7 @@ def test_route_justificatif(api_headers):
|
|||||||
|
|
||||||
|
|
||||||
def test_route_justificatifs(api_headers):
|
def test_route_justificatifs(api_headers):
|
||||||
|
"""test de la route /justificatifs/<etudid:int>"""
|
||||||
# Bon fonctionnement
|
# Bon fonctionnement
|
||||||
|
|
||||||
data = GET(path=f"/justificatifs/{ETUDID}", headers=api_headers)
|
data = GET(path=f"/justificatifs/{ETUDID}", headers=api_headers)
|
||||||
@ -117,7 +160,7 @@ def test_route_justificatifs(api_headers):
|
|||||||
|
|
||||||
|
|
||||||
def test_route_create(api_headers):
|
def test_route_create(api_headers):
|
||||||
|
"""test de la route /justificatif/<justif_id:int>/create"""
|
||||||
# -== Unique ==-
|
# -== Unique ==-
|
||||||
|
|
||||||
# Bon fonctionnement
|
# Bon fonctionnement
|
||||||
@ -198,7 +241,7 @@ def test_route_create(api_headers):
|
|||||||
|
|
||||||
|
|
||||||
def test_route_edit(api_headers):
|
def test_route_edit(api_headers):
|
||||||
|
"""test de la route /justificatif/<justif_id:int>/edit"""
|
||||||
# Bon fonctionnement
|
# Bon fonctionnement
|
||||||
|
|
||||||
data = {"etat": "modifie", "raison": "test"}
|
data = {"etat": "modifie", "raison": "test"}
|
||||||
@ -209,8 +252,6 @@ def test_route_edit(api_headers):
|
|||||||
res = POST_JSON(f"/justificatif/{TO_REMOVE[1]}/edit", data, api_headers)
|
res = POST_JSON(f"/justificatif/{TO_REMOVE[1]}/edit", data, api_headers)
|
||||||
assert res == {"OK": True}
|
assert res == {"OK": True}
|
||||||
|
|
||||||
# TODO: Modification date deb / fin
|
|
||||||
|
|
||||||
# Mauvais fonctionnement
|
# Mauvais fonctionnement
|
||||||
|
|
||||||
check_failure_post(f"/justificatif/{FAUX}/edit", api_headers, data)
|
check_failure_post(f"/justificatif/{FAUX}/edit", api_headers, data)
|
||||||
@ -224,6 +265,7 @@ def test_route_edit(api_headers):
|
|||||||
|
|
||||||
|
|
||||||
def test_route_delete(api_headers):
|
def test_route_delete(api_headers):
|
||||||
|
"""test de la route /justificatif/delete"""
|
||||||
# -== Unique ==-
|
# -== Unique ==-
|
||||||
|
|
||||||
# Bon fonctionnement
|
# Bon fonctionnement
|
||||||
@ -273,18 +315,18 @@ def send_file(justif_id: int, filename: str, headers):
|
|||||||
Envoi un fichier vers la route d'importation
|
Envoi un fichier vers la route d'importation
|
||||||
"""
|
"""
|
||||||
with open(filename, "rb") as file:
|
with open(filename, "rb") as file:
|
||||||
url: str = API_URL + f"/justificatif/import/{justif_id}"
|
url: str = API_URL + f"/justificatif/{justif_id}/import"
|
||||||
r = requests.post(
|
req = requests.post(
|
||||||
url,
|
url,
|
||||||
files={filename: file},
|
files={filename: file},
|
||||||
headers=headers,
|
headers=headers,
|
||||||
verify=CHECK_CERTIFICATE,
|
verify=CHECK_CERTIFICATE,
|
||||||
)
|
)
|
||||||
|
|
||||||
if r.status_code != 200:
|
if req.status_code != 200:
|
||||||
raise APIError(f"erreur status={r.status_code} !", r.json())
|
raise APIError(f"erreur status={req.status_code} !", req.json())
|
||||||
else:
|
|
||||||
return r.json()
|
return req.json()
|
||||||
|
|
||||||
|
|
||||||
def check_failure_send(
|
def check_failure_send(
|
||||||
@ -293,8 +335,21 @@ def check_failure_send(
|
|||||||
filename: str = "tests/api/test_api_justificatif.txt",
|
filename: str = "tests/api/test_api_justificatif.txt",
|
||||||
err: str = None,
|
err: str = None,
|
||||||
):
|
):
|
||||||
|
"""
|
||||||
|
Vérifie si l'envoie d'un fichier renvoie bien un 404
|
||||||
|
|
||||||
|
Args:
|
||||||
|
justif_id (int): l'id du justificatif
|
||||||
|
headers (dict): token d'auth de l'api
|
||||||
|
filename (str, optional): le chemin vers le fichier.
|
||||||
|
Defaults to "tests/api/test_api_justificatif.txt".
|
||||||
|
err (str, optional): l'erreur attendue.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
APIError: Si l'envoie fonction (mauvais comportement)
|
||||||
|
"""
|
||||||
try:
|
try:
|
||||||
data = send_file(justif_id, filename, headers)
|
send_file(justif_id, filename, headers)
|
||||||
# ^ Renvoi un 404
|
# ^ Renvoi un 404
|
||||||
except APIError as api_err:
|
except APIError as api_err:
|
||||||
if err is not None:
|
if err is not None:
|
||||||
@ -304,6 +359,7 @@ def check_failure_send(
|
|||||||
|
|
||||||
|
|
||||||
def test_import_justificatif(api_headers):
|
def test_import_justificatif(api_headers):
|
||||||
|
"""test de la route /justificatif/<justif_id:int>/import"""
|
||||||
|
|
||||||
# Bon fonctionnement
|
# Bon fonctionnement
|
||||||
|
|
||||||
@ -324,31 +380,45 @@ def test_import_justificatif(api_headers):
|
|||||||
|
|
||||||
|
|
||||||
def test_list_justificatifs(api_headers):
|
def test_list_justificatifs(api_headers):
|
||||||
|
"""test de la route /justificatif/<justif_id:int>/list"""
|
||||||
|
|
||||||
# Bon fonctionnement
|
# Bon fonctionnement
|
||||||
|
|
||||||
res: list = GET("/justificatif/list/1", api_headers)
|
res: list = GET("/justificatif/1/list", api_headers)
|
||||||
|
|
||||||
assert isinstance(res, list)
|
assert isinstance(res, list)
|
||||||
assert len(res) == 2
|
assert len(res) == 2
|
||||||
|
|
||||||
res: list = GET("/justificatif/list/2", api_headers)
|
res: list = GET("/justificatif/2/list", api_headers)
|
||||||
|
|
||||||
assert isinstance(res, list)
|
assert isinstance(res, list)
|
||||||
assert len(res) == 0
|
assert len(res) == 0
|
||||||
|
|
||||||
# Mauvais fonctionnement
|
# Mauvais fonctionnement
|
||||||
|
|
||||||
check_failure_get(f"/justificatif/list/{FAUX}", api_headers)
|
check_failure_get(f"/justificatif/{FAUX}/list", api_headers)
|
||||||
|
|
||||||
|
|
||||||
def post_export(id: int, fname: str, api_headers):
|
def post_export(justif_id: int, fname: str, api_headers):
|
||||||
url: str = API_URL + f"/justificatif/export/{id}/{fname}"
|
"""
|
||||||
|
Envoie une requête poste sans data et la retourne
|
||||||
|
|
||||||
|
Args:
|
||||||
|
id (int): justif_id
|
||||||
|
fname (str): nom du fichier (coté serv)
|
||||||
|
api_headers (dict): token auth de l'api
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
request: la réponse de l'api
|
||||||
|
"""
|
||||||
|
url: str = API_URL + f"/justificatif/{justif_id}/export/{fname}"
|
||||||
res = requests.post(url, headers=api_headers)
|
res = requests.post(url, headers=api_headers)
|
||||||
return res
|
return res
|
||||||
|
|
||||||
|
|
||||||
def test_export(api_headers):
|
def test_export(api_headers):
|
||||||
|
"""test de la route /justificatif/<justif_id:int>/export/<filename:str>"""
|
||||||
|
|
||||||
# Bon fonctionnement
|
# Bon fonctionnement
|
||||||
|
|
||||||
assert post_export(1, "test_api_justificatif.txt", api_headers).status_code == 200
|
assert post_export(1, "test_api_justificatif.txt", api_headers).status_code == 200
|
||||||
@ -362,6 +432,7 @@ def test_export(api_headers):
|
|||||||
|
|
||||||
|
|
||||||
def test_remove_justificatif(api_headers):
|
def test_remove_justificatif(api_headers):
|
||||||
|
"""test de la route /justificatif/<justif_id:int>/remove"""
|
||||||
|
|
||||||
# Bon fonctionnement
|
# Bon fonctionnement
|
||||||
|
|
||||||
@ -370,39 +441,41 @@ def test_remove_justificatif(api_headers):
|
|||||||
filename: str = "tests/api/test_api_justificatif2.txt"
|
filename: str = "tests/api/test_api_justificatif2.txt"
|
||||||
send_file(2, filename, api_headers)
|
send_file(2, filename, api_headers)
|
||||||
|
|
||||||
res: dict = POST_JSON("/justificatif/remove/1", {"remove": "all"}, api_headers)
|
res: dict = POST_JSON("/justificatif/1/remove", {"remove": "all"}, api_headers)
|
||||||
assert res == {"response": "removed"}
|
assert res == {"response": "removed"}
|
||||||
assert len(GET("/justificatif/list/1", api_headers)) == 0
|
assert len(GET("/justificatif/1/list", api_headers)) == 0
|
||||||
|
|
||||||
res: dict = POST_JSON(
|
res: dict = POST_JSON(
|
||||||
"/justificatif/remove/2",
|
"/justificatif/2/remove",
|
||||||
{"remove": "list", "filenames": ["test_api_justificatif2.txt"]},
|
{"remove": "list", "filenames": ["test_api_justificatif2.txt"]},
|
||||||
api_headers,
|
api_headers,
|
||||||
)
|
)
|
||||||
assert res == {"response": "removed"}
|
assert res == {"response": "removed"}
|
||||||
assert len(GET("/justificatif/list/2", api_headers)) == 1
|
assert len(GET("/justificatif/2/list", api_headers)) == 1
|
||||||
|
|
||||||
res: dict = POST_JSON(
|
res: dict = POST_JSON(
|
||||||
"/justificatif/remove/2",
|
"/justificatif/2/remove",
|
||||||
{"remove": "list", "filenames": ["test_api_justificatif.txt"]},
|
{"remove": "list", "filenames": ["test_api_justificatif.txt"]},
|
||||||
api_headers,
|
api_headers,
|
||||||
)
|
)
|
||||||
assert res == {"response": "removed"}
|
assert res == {"response": "removed"}
|
||||||
assert len(GET("/justificatif/list/2", api_headers)) == 0
|
assert len(GET("/justificatif/2/list", api_headers)) == 0
|
||||||
|
|
||||||
# Mauvais fonctionnement
|
# Mauvais fonctionnement
|
||||||
|
|
||||||
check_failure_post("/justificatif/remove/2", api_headers, {})
|
check_failure_post("/justificatif/2/remove", api_headers, {})
|
||||||
check_failure_post(f"/justificatif/remove/{FAUX}", api_headers, {"remove": "all"})
|
check_failure_post(f"/justificatif/{FAUX}/remove", api_headers, {"remove": "all"})
|
||||||
check_failure_post("/justificatif/remove/1", api_headers, {"remove": "all"})
|
check_failure_post("/justificatif/1/remove", api_headers, {"remove": "all"})
|
||||||
|
|
||||||
|
|
||||||
def test_justified(api_headers):
|
def test_justifies(api_headers):
|
||||||
|
"""test la route /justificatif/<justif_id:int>/justifies"""
|
||||||
|
|
||||||
# Bon fonctionnement
|
# Bon fonctionnement
|
||||||
|
|
||||||
res: list = GET("/justificatif/justified/1", api_headers)
|
res: list = GET("/justificatif/1/justifies", api_headers)
|
||||||
assert isinstance(res, list)
|
assert isinstance(res, list)
|
||||||
|
|
||||||
# Mauvais fonctionnement
|
# Mauvais fonctionnement
|
||||||
|
|
||||||
check_failure_get(f"/justificatif/justified/{FAUX}", api_headers)
|
check_failure_get(f"/justificatif/{FAUX}/justifies", api_headers)
|
||||||
|
@ -26,6 +26,8 @@ from tools import migrate_abs_to_assiduites, downgrade_module
|
|||||||
|
|
||||||
|
|
||||||
class BiInt(int, scu.BiDirectionalEnum):
|
class BiInt(int, scu.BiDirectionalEnum):
|
||||||
|
"""Classe pour tester la classe BiDirectionalEnum"""
|
||||||
|
|
||||||
A = 1
|
A = 1
|
||||||
B = 2
|
B = 2
|
||||||
|
|
||||||
@ -136,7 +138,7 @@ def test_general(test_client):
|
|||||||
etud_faux_dict = g_fake.create_etud(code_nip=None, prenom="etudfaux")
|
etud_faux_dict = g_fake.create_etud(code_nip=None, prenom="etudfaux")
|
||||||
etud_faux = Identite.query.filter_by(id=etud_faux_dict["id"]).first()
|
etud_faux = Identite.query.filter_by(id=etud_faux_dict["id"]).first()
|
||||||
|
|
||||||
verif_migration_abs_assiduites(g_fake)
|
verif_migration_abs_assiduites()
|
||||||
|
|
||||||
ajouter_assiduites(etuds, moduleimpls, etud_faux)
|
ajouter_assiduites(etuds, moduleimpls, etud_faux)
|
||||||
justificatifs: list[Justificatif] = ajouter_justificatifs(etuds[0])
|
justificatifs: list[Justificatif] = ajouter_justificatifs(etuds[0])
|
||||||
@ -148,7 +150,8 @@ def test_general(test_client):
|
|||||||
editer_supprimer_justificatif(etuds[0])
|
editer_supprimer_justificatif(etuds[0])
|
||||||
|
|
||||||
|
|
||||||
def verif_migration_abs_assiduites(g_fake):
|
def verif_migration_abs_assiduites():
|
||||||
|
"""Vérification que le script de migration fonctionne correctement"""
|
||||||
downgrade_module(assiduites=True, justificatifs=True)
|
downgrade_module(assiduites=True, justificatifs=True)
|
||||||
|
|
||||||
etudid: int = 1
|
etudid: int = 1
|
||||||
@ -179,7 +182,7 @@ def verif_migration_abs_assiduites(g_fake):
|
|||||||
"02/01/2023",
|
"02/01/2023",
|
||||||
"10/01/2023",
|
"10/01/2023",
|
||||||
2,
|
2,
|
||||||
), # 2 justificatif 02/01: 08h -> 06/01: 18h & justificatif 09/01: 08h -> 10/01: 18h | 14dj
|
), # 2 justificatif 02/01: 08h -> 06/01: 18h & justificatif 09/01: 08h -> 10/01: 18h | 14dj
|
||||||
(
|
(
|
||||||
"19/01/2023",
|
"19/01/2023",
|
||||||
"19/01/2023",
|
"19/01/2023",
|
||||||
@ -203,12 +206,14 @@ def verif_migration_abs_assiduites(g_fake):
|
|||||||
assert Assiduite.query.count() == 6, "Erreur migration assiduites"
|
assert Assiduite.query.count() == 6, "Erreur migration assiduites"
|
||||||
assert Justificatif.query.count() == 4, "Erreur migration justificatifs"
|
assert Justificatif.query.count() == 4, "Erreur migration justificatifs"
|
||||||
|
|
||||||
essais_cache(etudid, g_fake)
|
essais_cache(etudid)
|
||||||
|
|
||||||
downgrade_module(assiduites=True, justificatifs=True)
|
downgrade_module(assiduites=True, justificatifs=True)
|
||||||
|
|
||||||
|
|
||||||
def essais_cache(etudid, g_fake):
|
def essais_cache(etudid):
|
||||||
|
"""Vérification des fonctionnalités du cache TODO:WIP"""
|
||||||
|
|
||||||
date_deb: str = "2023-01-01T07:00"
|
date_deb: str = "2023-01-01T07:00"
|
||||||
date_fin: str = "2023-03-31T19:00"
|
date_fin: str = "2023-03-31T19:00"
|
||||||
|
|
||||||
@ -231,6 +236,7 @@ def essais_cache(etudid, g_fake):
|
|||||||
|
|
||||||
|
|
||||||
def ajouter_justificatifs(etud):
|
def ajouter_justificatifs(etud):
|
||||||
|
"""test de l'ajout des justificatifs"""
|
||||||
|
|
||||||
obj_justificatifs = [
|
obj_justificatifs = [
|
||||||
{
|
{
|
||||||
@ -318,7 +324,7 @@ def verifier_filtrage_justificatifs(etud: Identite, justificatifs: list[Justific
|
|||||||
), "Filtrage de l'état 'valide' mauvais"
|
), "Filtrage de l'état 'valide' mauvais"
|
||||||
assert (
|
assert (
|
||||||
scass.filter_justificatifs_by_etat(etud.justificatifs, "attente").count() == 1
|
scass.filter_justificatifs_by_etat(etud.justificatifs, "attente").count() == 1
|
||||||
), f"Filtrage de l'état 'attente' mauvais"
|
), "Filtrage de l'état 'attente' mauvais"
|
||||||
assert (
|
assert (
|
||||||
scass.filter_justificatifs_by_etat(etud.justificatifs, "modifie").count() == 1
|
scass.filter_justificatifs_by_etat(etud.justificatifs, "modifie").count() == 1
|
||||||
), "Filtrage de l'état 'modifie' mauvais"
|
), "Filtrage de l'état 'modifie' mauvais"
|
||||||
@ -404,7 +410,7 @@ def verifier_filtrage_justificatifs(etud: Identite, justificatifs: list[Justific
|
|||||||
# Justifications des assiduites
|
# Justifications des assiduites
|
||||||
|
|
||||||
assert len(scass.justifies(justificatifs[2])) == 1, "Justifications mauvais"
|
assert len(scass.justifies(justificatifs[2])) == 1, "Justifications mauvais"
|
||||||
assert len(scass.justifies(justificatifs[0])) == 0, f"Justifications mauvais"
|
assert len(scass.justifies(justificatifs[0])) == 0, "Justifications mauvais"
|
||||||
|
|
||||||
|
|
||||||
def editer_supprimer_justificatif(etud: Identite):
|
def editer_supprimer_justificatif(etud: Identite):
|
||||||
|
@ -1,12 +1,27 @@
|
|||||||
|
"""
|
||||||
|
Commande permettant de supprimer les assiduités et les justificatifs
|
||||||
|
|
||||||
|
Ecrit par Matthias HARTMANN
|
||||||
|
"""
|
||||||
|
|
||||||
from app import db
|
from app import db
|
||||||
from app.models import Justificatif, Assiduite, Departement
|
from app.models import Justificatif, Assiduite, Departement
|
||||||
from app.scodoc.sco_archives_justificatifs import JustificatifArchiver
|
from app.scodoc.sco_archives_justificatifs import JustificatifArchiver
|
||||||
from app.scodoc.sco_utils import ProgressBarColors
|
from app.scodoc.sco_utils import TerminalColor
|
||||||
|
|
||||||
|
|
||||||
def downgrade_module(
|
def downgrade_module(
|
||||||
dept: str = None, assiduites: bool = False, justificatifs: bool = False
|
dept: str = None, assiduites: bool = False, justificatifs: bool = False
|
||||||
):
|
):
|
||||||
|
"""
|
||||||
|
Supprime les assiduités et/ou justificatifs du dept sélectionné ou de tous les départements
|
||||||
|
|
||||||
|
Args:
|
||||||
|
dept (str, optional): l'acronym du département. Par défaut tous les départements.
|
||||||
|
assiduites (bool, optional): suppression des assiduités. Par défaut : Non
|
||||||
|
justificatifs (bool, optional): supression des justificatifs. Par défaut : Non
|
||||||
|
"""
|
||||||
|
|
||||||
dept_etudid: list[int] = None
|
dept_etudid: list[int] = None
|
||||||
dept_id: int = None
|
dept_id: int = None
|
||||||
|
|
||||||
@ -28,7 +43,7 @@ def downgrade_module(
|
|||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
||||||
print(
|
print(
|
||||||
f"{ProgressBarColors.GREEN}Le module assiduité a bien été remis à zero.{ProgressBarColors.RESET}"
|
f"{TerminalColor.GREEN}Le module assiduité a bien été remis à zero.{TerminalColor.RESET}"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,32 +1,38 @@
|
|||||||
# Script de migration des données de la base "absences" -> "assiduites"/"justificatifs"
|
"""
|
||||||
|
Script de migration des données de la base "absences" -> "assiduites"/"justificatifs"
|
||||||
|
|
||||||
|
Ecrit par Matthias HARTMANN
|
||||||
|
"""
|
||||||
|
from datetime import date, datetime, time, timedelta
|
||||||
|
from json import dump, dumps
|
||||||
|
from sqlalchemy import not_
|
||||||
|
|
||||||
from app import db
|
from app import db
|
||||||
from app.profiler import Profiler
|
|
||||||
from app.models import (
|
from app.models import (
|
||||||
Assiduite,
|
|
||||||
Justificatif,
|
|
||||||
Absence,
|
Absence,
|
||||||
Identite,
|
Assiduite,
|
||||||
ModuleImplInscription,
|
|
||||||
Departement,
|
Departement,
|
||||||
|
Identite,
|
||||||
|
Justificatif,
|
||||||
|
ModuleImplInscription,
|
||||||
)
|
)
|
||||||
|
from app.profiler import Profiler
|
||||||
from app.scodoc.sco_utils import (
|
from app.scodoc.sco_utils import (
|
||||||
EtatAssiduite,
|
EtatAssiduite,
|
||||||
EtatJustificatif,
|
EtatJustificatif,
|
||||||
|
TerminalColor,
|
||||||
localize_datetime,
|
localize_datetime,
|
||||||
ProgressBarColors,
|
print_progress_bar,
|
||||||
printProgressBar,
|
|
||||||
)
|
)
|
||||||
from datetime import time, datetime, date, timedelta
|
|
||||||
from json import dump, dumps
|
|
||||||
|
|
||||||
from sqlalchemy import not_
|
|
||||||
|
|
||||||
|
|
||||||
class _Merger:
|
class _Merger:
|
||||||
""""""
|
"""pour typage"""
|
||||||
|
|
||||||
|
|
||||||
class _glob:
|
class _glob:
|
||||||
|
"""variables globales du script"""
|
||||||
|
|
||||||
PROBLEMS: dict[int, list[str]] = {}
|
PROBLEMS: dict[int, list[str]] = {}
|
||||||
CURRENT_ETU: list = []
|
CURRENT_ETU: list = []
|
||||||
MODULES: list[tuple[int, int]] = []
|
MODULES: list[tuple[int, int]] = []
|
||||||
@ -41,42 +47,43 @@ class _glob:
|
|||||||
|
|
||||||
|
|
||||||
class _Merger:
|
class _Merger:
|
||||||
def __init__(self, abs: Absence, est_abs: bool) -> None:
|
def __init__(self, abs_: Absence, est_abs: bool) -> None:
|
||||||
self.deb = (abs.jour, abs.matin)
|
self.deb = (abs_.jour, abs_.matin)
|
||||||
self.fin = (abs.jour, abs.matin)
|
self.fin = (abs_.jour, abs_.matin)
|
||||||
self.moduleimpl = abs.moduleimpl_id
|
self.moduleimpl = abs_.moduleimpl_id
|
||||||
self.etudid = abs.etudid
|
self.etudid = abs_.etudid
|
||||||
self.est_abs = est_abs
|
self.est_abs = est_abs
|
||||||
self.raison = abs.description
|
self.raison = abs_.description
|
||||||
|
|
||||||
def merge(self, abs: Absence) -> bool:
|
def merge(self, abs_: Absence) -> bool:
|
||||||
|
"""Fusionne les absences"""
|
||||||
|
|
||||||
if self.etudid != abs.etudid:
|
if self.etudid != abs_.etudid:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# Cas d'une même absence enregistrée plusieurs fois
|
# Cas d'une même absence enregistrée plusieurs fois
|
||||||
if self.fin == (abs.jour, abs.matin):
|
if self.fin == (abs_.jour, abs_.matin):
|
||||||
self.moduleimpl = None
|
self.moduleimpl = None
|
||||||
else:
|
else:
|
||||||
if self.fin[1]:
|
if self.fin[1]:
|
||||||
if abs.jour != self.fin[0]:
|
if abs_.jour != self.fin[0]:
|
||||||
return False
|
return False
|
||||||
else:
|
else:
|
||||||
day_after: date = abs.jour - timedelta(days=1) == self.fin[0]
|
day_after: date = abs_.jour - timedelta(days=1) == self.fin[0]
|
||||||
if not (day_after and abs.matin):
|
if not (day_after and abs_.matin):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
self.fin = (abs.jour, abs.matin)
|
self.fin = (abs_.jour, abs_.matin)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _tuple_to_date(tuple, end=False):
|
def _tuple_to_date(couple: tuple[date, bool], end=False):
|
||||||
if tuple[1]:
|
if couple[1]:
|
||||||
time_ = _glob.NOON if end else _glob.MORNING
|
time_ = _glob.NOON if end else _glob.MORNING
|
||||||
date_ = datetime.combine(tuple[0], time_)
|
date_ = datetime.combine(couple[0], time_)
|
||||||
else:
|
else:
|
||||||
time_ = _glob.EVENING if end else _glob.NOON
|
time_ = _glob.EVENING if end else _glob.NOON
|
||||||
date_ = datetime.combine(tuple[0], time_)
|
date_ = datetime.combine(couple[0], time_)
|
||||||
d = localize_datetime(date_)
|
d = localize_datetime(date_)
|
||||||
return d
|
return d
|
||||||
|
|
||||||
@ -108,20 +115,21 @@ class _Merger:
|
|||||||
return retour
|
return retour
|
||||||
|
|
||||||
def export(self):
|
def export(self):
|
||||||
objects = []
|
"""Génère un nouvel objet Assiduité ou Justificatif"""
|
||||||
|
obj: Assiduite or Justificatif = None
|
||||||
if self.est_abs:
|
if self.est_abs:
|
||||||
_glob.COMPTE[0] += 1
|
_glob.COMPTE[0] += 1
|
||||||
objects.append(self._to_assi())
|
obj = self._to_assi()
|
||||||
else:
|
else:
|
||||||
_glob.COMPTE[1] += 1
|
_glob.COMPTE[1] += 1
|
||||||
objects.append(self._to_justif())
|
obj = self._to_justif()
|
||||||
|
|
||||||
db.session.add_all(objects)
|
db.session.add(obj)
|
||||||
|
|
||||||
|
|
||||||
class _Statistics:
|
class _Statistics:
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
self.object: dict = {"total": 0}
|
self.object: dict[str, dict or int] = {"total": 0}
|
||||||
self.year: int = None
|
self.year: int = None
|
||||||
|
|
||||||
def __set_year(self, year: int):
|
def __set_year(self, year: int):
|
||||||
@ -138,41 +146,46 @@ class _Statistics:
|
|||||||
self.object[self.year]["etuds_inexistant"].append(etudid)
|
self.object[self.year]["etuds_inexistant"].append(etudid)
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def __add_abs(self, abs: int, err: str):
|
def __add_abs(self, abs_: int, err: str):
|
||||||
if abs not in self.object[self.year]["abs_invalide"]:
|
if abs_ not in self.object[self.year]["abs_invalide"]:
|
||||||
self.object[self.year]["abs_invalide"][abs] = [err]
|
self.object[self.year]["abs_invalide"][abs_] = [err]
|
||||||
else:
|
else:
|
||||||
self.object[self.year]["abs_invalide"][abs].append(err)
|
self.object[self.year]["abs_invalide"][abs_].append(err)
|
||||||
|
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def add_problem(self, abs: Absence, err: str):
|
def add_problem(self, abs_: Absence, err: str):
|
||||||
abs.jour: date
|
"""Ajoute un nouveau problème dans les statistiques"""
|
||||||
pivot: date = date(abs.jour.year, 9, 15)
|
abs_.jour: date
|
||||||
year: int = abs.jour.year
|
pivot: date = date(abs_.jour.year, 9, 15)
|
||||||
if pivot < abs.jour:
|
year: int = abs_.jour.year
|
||||||
|
if pivot < abs_.jour:
|
||||||
year += 1
|
year += 1
|
||||||
self.__set_year(year)
|
self.__set_year(year)
|
||||||
|
|
||||||
if err == "Etudiant inexistant":
|
if err == "Etudiant inexistant":
|
||||||
self.__add_etud(abs.etudid)
|
self.__add_etud(abs_.etudid)
|
||||||
else:
|
else:
|
||||||
self.__add_abs(abs.id, err)
|
self.__add_abs(abs_.id, err)
|
||||||
|
|
||||||
self.object["total"] += 1
|
self.object["total"] += 1
|
||||||
|
|
||||||
def compute_stats(self) -> dict:
|
def compute_stats(self) -> dict:
|
||||||
|
"""Comptage des statistiques"""
|
||||||
stats: dict = {"total": self.object["total"]}
|
stats: dict = {"total": self.object["total"]}
|
||||||
for year in self.object:
|
for year, item in self.object.items():
|
||||||
|
|
||||||
if year == "total":
|
if year == "total":
|
||||||
continue
|
continue
|
||||||
|
|
||||||
stats[year] = {}
|
stats[year] = {}
|
||||||
stats[year]["etuds_inexistant"] = len(self.object[year]["etuds_inexistant"])
|
stats[year]["etuds_inexistant"] = len(item["etuds_inexistant"])
|
||||||
stats[year]["abs_invalide"] = len(self.object[year]["abs_invalide"])
|
stats[year]["abs_invalide"] = len(item["abs_invalide"])
|
||||||
|
|
||||||
return stats
|
return stats
|
||||||
|
|
||||||
def export(self, file):
|
def export(self, file):
|
||||||
|
"""Sérialise les statistiques dans un fichier"""
|
||||||
dump(self.object, file, indent=2)
|
dump(self.object, file, indent=2)
|
||||||
|
|
||||||
|
|
||||||
@ -242,20 +255,20 @@ def migrate_abs_to_assiduites(
|
|||||||
absences_len: int = absences.count()
|
absences_len: int = absences.count()
|
||||||
|
|
||||||
print(
|
print(
|
||||||
f"{ProgressBarColors.BLUE}{absences_len} absences vont être migrées{ProgressBarColors.RESET}"
|
f"{TerminalColor.BLUE}{absences_len} absences vont être migrées{TerminalColor.RESET}"
|
||||||
)
|
)
|
||||||
|
|
||||||
printProgressBar(0, absences_len, "Progression", "effectué", autosize=True)
|
print_progress_bar(0, absences_len, "Progression", "effectué", autosize=True)
|
||||||
|
|
||||||
for i, abs in enumerate(absences):
|
for i, abs_ in enumerate(absences):
|
||||||
|
|
||||||
try:
|
try:
|
||||||
_from_abs_to_assiduite_justificatif(abs)
|
_from_abs_to_assiduite_justificatif(abs_)
|
||||||
except Exception as e:
|
except ValueError as e:
|
||||||
stats.add_problem(abs, e.args[0])
|
stats.add_problem(abs_, e.args[0])
|
||||||
|
|
||||||
if i % 10 == 0:
|
if i % 10 == 0:
|
||||||
printProgressBar(
|
print_progress_bar(
|
||||||
i,
|
i,
|
||||||
absences_len,
|
absences_len,
|
||||||
"Progression",
|
"Progression",
|
||||||
@ -264,7 +277,7 @@ def migrate_abs_to_assiduites(
|
|||||||
)
|
)
|
||||||
|
|
||||||
if i % 1000 == 0:
|
if i % 1000 == 0:
|
||||||
printProgressBar(
|
print_progress_bar(
|
||||||
i,
|
i,
|
||||||
absences_len,
|
absences_len,
|
||||||
"Progression",
|
"Progression",
|
||||||
@ -278,7 +291,7 @@ def migrate_abs_to_assiduites(
|
|||||||
|
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
||||||
printProgressBar(
|
print_progress_bar(
|
||||||
absences_len,
|
absences_len,
|
||||||
absences_len,
|
absences_len,
|
||||||
"Progression",
|
"Progression",
|
||||||
@ -290,14 +303,14 @@ def migrate_abs_to_assiduites(
|
|||||||
|
|
||||||
statistiques: dict = stats.compute_stats()
|
statistiques: dict = stats.compute_stats()
|
||||||
print(
|
print(
|
||||||
f"{ProgressBarColors.GREEN}La migration a pris {time_elapsed.elapsed():.2f} secondes {ProgressBarColors.RESET}"
|
f"{TerminalColor.GREEN}La migration a pris {time_elapsed.elapsed():.2f} secondes {TerminalColor.RESET}"
|
||||||
)
|
)
|
||||||
|
|
||||||
print(
|
print(
|
||||||
f"{ProgressBarColors.RED}{statistiques['total']} absences qui n'ont pas pu être migrées."
|
f"{TerminalColor.RED}{statistiques['total']} absences qui n'ont pas pu être migrées."
|
||||||
)
|
)
|
||||||
print(
|
print(
|
||||||
f"Vous retrouverez un fichier json {ProgressBarColors.GREEN}/opt/scodoc-data/log/scodoc_migration_abs.json{ProgressBarColors.RED} contenant les problèmes de migrations"
|
f"Vous retrouverez un fichier json {TerminalColor.GREEN}/opt/scodoc-data/log/scodoc_migration_abs.json{TerminalColor.RED} contenant les problèmes de migrations"
|
||||||
)
|
)
|
||||||
with open(
|
with open(
|
||||||
"/opt/scodoc-data/log/scodoc_migration_abs.json", "w", encoding="utf-8"
|
"/opt/scodoc-data/log/scodoc_migration_abs.json", "w", encoding="utf-8"
|
||||||
@ -305,7 +318,7 @@ def migrate_abs_to_assiduites(
|
|||||||
stats.export(file)
|
stats.export(file)
|
||||||
|
|
||||||
print(
|
print(
|
||||||
f"{ProgressBarColors.CYAN}{_glob.COMPTE[0]} assiduités et {_glob.COMPTE[1]} justificatifs ont été générés.{ProgressBarColors.RESET}"
|
f"{TerminalColor.CYAN}{_glob.COMPTE[0]} assiduités et {_glob.COMPTE[1]} justificatifs ont été générés.{TerminalColor.RESET}"
|
||||||
)
|
)
|
||||||
|
|
||||||
print(dumps(statistiques, indent=2))
|
print(dumps(statistiques, indent=2))
|
||||||
@ -316,7 +329,7 @@ def _from_abs_to_assiduite_justificatif(_abs: Absence):
|
|||||||
if _abs.etudid not in _glob.CURRENT_ETU:
|
if _abs.etudid not in _glob.CURRENT_ETU:
|
||||||
etud: Identite = Identite.query.filter_by(id=_abs.etudid).first()
|
etud: Identite = Identite.query.filter_by(id=_abs.etudid).first()
|
||||||
if etud is None:
|
if etud is None:
|
||||||
raise Exception("Etudiant inexistant")
|
raise ValueError("Etudiant inexistant")
|
||||||
_glob.CURRENT_ETU.append(_abs.etudid)
|
_glob.CURRENT_ETU.append(_abs.etudid)
|
||||||
|
|
||||||
if _abs.estabs:
|
if _abs.estabs:
|
||||||
@ -331,7 +344,7 @@ def _from_abs_to_assiduite_justificatif(_abs: Absence):
|
|||||||
).first()
|
).first()
|
||||||
)
|
)
|
||||||
if moduleimpl_inscription is None:
|
if moduleimpl_inscription is None:
|
||||||
raise Exception("Moduleimpl_id incorrect ou étudiant non inscrit")
|
raise ValueError("Moduleimpl_id incorrect ou étudiant non inscrit")
|
||||||
|
|
||||||
if _glob.MERGER_ASSI is None:
|
if _glob.MERGER_ASSI is None:
|
||||||
_glob.MERGER_ASSI = _Merger(_abs, True)
|
_glob.MERGER_ASSI = _Merger(_abs, True)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user