1
0
forked from ScoDoc/ScoDoc

simplification enum + fonction generic + revisions

This commit is contained in:
iziram 2023-02-02 22:20:25 +01:00
parent 547040bb93
commit a7b856b1ec
9 changed files with 288 additions and 141 deletions

View File

@ -40,14 +40,7 @@ def assiduite(assiduite_id: int = None):
} }
""" """
query = Assiduite.query.filter_by(id=assiduite_id) return scu.get_model_api_object(Assiduite, assiduite_id)
# if g.scodoc_dept:
# query = query.join(Identite).filter_by(dept_id=g.scodoc_dept_id)
assiduite_query = query.first_or_404()
data = assiduite_query.to_dict()
return jsonify(_change_etat(data))
@bp.route("/assiduites/<int:etudid>/count", defaults={"with_query": False}) @bp.route("/assiduites/<int:etudid>/count", defaults={"with_query": False})
@ -164,8 +157,8 @@ def assiduites(etudid: int = None, with_query: bool = False):
data_set: list[dict] = [] data_set: list[dict] = []
for ass in assiduites_query.all(): for ass in assiduites_query.all():
data = ass.to_dict() data = ass.to_dict(format_api=True)
data_set.append(_change_etat(data)) data_set.append(data)
return jsonify(data_set) return jsonify(data_set)
@ -202,8 +195,8 @@ def assiduites_formsemestre(formsemestre_id: int, with_query: bool = False):
data_set: list[dict] = [] data_set: list[dict] = []
for ass in assiduites_query.all(): for ass in assiduites_query.all():
data = ass.to_dict() data = ass.to_dict(format_api=True)
data_set.append(_change_etat(data)) data_set.append(data)
return jsonify(data_set) return jsonify(data_set)
@ -307,11 +300,10 @@ def _create_singular(
etat = data.get("etat", None) etat = data.get("etat", None)
if etat is None: if etat is None:
errors.append("param 'etat': manquant") errors.append("param 'etat': manquant")
elif etat not in scu.ETATS_ASSIDUITE: elif not scu.EtatAssiduite.contains(etat):
errors.append("param 'etat': invalide") errors.append("param 'etat': invalide")
data = _change_etat(data, False) etat = scu.EtatAssiduite.get(etat)
etat = data.get("etat", None)
# cas 2 : date_debut # cas 2 : date_debut
date_debut = data.get("date_debut", None) date_debut = data.get("date_debut", None)
@ -418,7 +410,7 @@ def _delete_singular(assiduite_id: int, database):
@scodoc @scodoc
@permission_required(Permission.ScoView) @permission_required(Permission.ScoView)
# @permission_required(Permission.ScoAssiduiteChange) # @permission_required(Permission.ScoAssiduiteChange)
def assiduite_cedit(assiduite_id: int): def assiduite_edit(assiduite_id: int):
""" """
Edition d'une assiduité à partir de son id Edition d'une assiduité à partir de son id
La requête doit avoir un content type "application/json": La requête doit avoir un content type "application/json":
@ -438,11 +430,11 @@ def assiduite_cedit(assiduite_id: int):
# Cas 1 : Etat # Cas 1 : Etat
if data.get("etat") is not None: if data.get("etat") is not None:
data = _change_etat(data, False) etat = scu.EtatAssiduite.get(data.get("etat"))
if data.get("etat") is None: if etat is None:
errors.append("param 'etat': invalide") errors.append("param 'etat': invalide")
else: else:
assiduite_unique.etat = data.get("etat") assiduite_unique.etat = etat
# Cas 2 : Moduleimpl_id # Cas 2 : Moduleimpl_id
moduleimpl_id = data.get("moduleimpl_id", False) moduleimpl_id = data.get("moduleimpl_id", False)
@ -478,13 +470,6 @@ def assiduite_cedit(assiduite_id: int):
# -- Utils -- # -- Utils --
def _change_etat(data: dict, from_int: bool = True):
"""change dans un json la valeur du champs état"""
if from_int:
data["etat"] = scu.ETAT_ASSIDUITE_NAME.get(data["etat"])
else:
data["etat"] = scu.ETATS_ASSIDUITE.get(data["etat"])
return data
def _count_manager(requested) -> tuple[str, dict]: def _count_manager(requested) -> tuple[str, dict]:

View File

@ -5,22 +5,22 @@
############################################################################## ##############################################################################
"""ScoDoc 9 API : Assiduités """ScoDoc 9 API : Assiduités
""" """
import os
from datetime import datetime from datetime import datetime
from flask import g, jsonify, request
from flask_login import login_required
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 from app import db
from app.api import api_bp as bp from app.api import api_bp as bp
from app.api import api_web_bp from app.api import api_web_bp
from app.scodoc.sco_exceptions import ScoValueError
from app.decorators import permission_required, scodoc from app.decorators import permission_required, scodoc
from app.models import Identite, Justificatif from app.models import Identite, Justificatif
from app.models.assiduites import is_period_conflicting
from app.scodoc.sco_archives_justificatifs import JustificatifArchiver from app.scodoc.sco_archives_justificatifs import JustificatifArchiver
from app.scodoc.sco_exceptions import ScoValueError
from app.scodoc.sco_permissions import Permission from app.scodoc.sco_permissions import Permission
from flask import g, jsonify, request
from flask_login import login_required
from app.scodoc.sco_utils import json_error from app.scodoc.sco_utils import json_error
@ -35,9 +35,8 @@ from app.scodoc.sco_utils import json_error
# return jsonify("done") # return jsonify("done")
# Partie Modèle # Partie Modèle
# TODO: justificatif
@bp.route("/justificatif/<int:justif_id>") @bp.route("/justificatif/<int:justif_id>")
@api_web_bp.route("/assiduite/<int:justif_id>") @api_web_bp.route("/justificatif/<int:justif_id>")
@scodoc @scodoc
@permission_required(Permission.ScoView) @permission_required(Permission.ScoView)
def justificatif(justif_id: int = None): def justificatif(justif_id: int = None):
@ -54,19 +53,12 @@ def justificatif(justif_id: int = None):
"raison": "une raison", "raison": "une raison",
"entry_date": "2022-10-31T08:00+01:00", "entry_date": "2022-10-31T08:00+01:00",
} }
""" """
query = Justificatif.query.filter_by(id=justif_id) return scu.get_model_api_object(Justificatif, justif_id)
if g.scodoc_dept:
query = query.join(Identite).filter_by(dept_id=g.scodoc_dept_id)
justificatif_unique = query.first_or_404()
data = justificatif_unique.to_dict()
return jsonify(_change_etat(data))
# TODO: justificatifs[-query]
@bp.route("/justificatifs/<int:etudid>", defaults={"with_query": False}) @bp.route("/justificatifs/<int:etudid>", defaults={"with_query": False})
@bp.route("/justificatifs/<int:etudid>/query", defaults={"with_query": True}) @bp.route("/justificatifs/<int:etudid>/query", defaults={"with_query": True})
@api_web_bp.route("/justificatifs/<int:etudid>", defaults={"with_query": False}) @api_web_bp.route("/justificatifs/<int:etudid>", defaults={"with_query": False})
@ -110,13 +102,12 @@ def justificatifs(etudid: int = None, with_query: bool = False):
data_set: list[dict] = [] data_set: list[dict] = []
for just in justificatifs_query.all(): for just in justificatifs_query.all():
data = just.to_dict() data = just.to_dict(format_api=True)
data_set.append(_change_etat(data)) data_set.append(data)
return jsonify(data_set) return jsonify(data_set)
# TODO: justificatif-create
@bp.route("/justificatif/<int:etudid>/create", methods=["POST"]) @bp.route("/justificatif/<int:etudid>/create", methods=["POST"])
@api_web_bp.route("/justificatif/<int:etudid>/create", methods=["POST"]) @api_web_bp.route("/justificatif/<int:etudid>/create", methods=["POST"])
@scodoc @scodoc
@ -173,11 +164,10 @@ def _create_singular(
etat = data.get("etat", None) etat = data.get("etat", None)
if etat is None: if etat is None:
errors.append("param 'etat': manquant") errors.append("param 'etat': manquant")
elif etat not in scu.ETATS_JUSTIFICATIF: elif not scu.EtatJustificatif.contains(etat):
errors.append("param 'etat': invalide") errors.append("param 'etat': invalide")
data = _change_etat(data, False) etat = scu.EtatJustificatif.get(etat)
etat = data.get("etat", None)
# cas 2 : date_debut # cas 2 : date_debut
date_debut = data.get("date_debut", None) date_debut = data.get("date_debut", None)
@ -224,7 +214,6 @@ def _create_singular(
) )
# TODO: justificatif-edit
@bp.route("/justificatif/<int:justif_id>/edit", methods=["POST"]) @bp.route("/justificatif/<int:justif_id>/edit", methods=["POST"])
@api_web_bp.route("/justificatif/<int:justif_id>/edit", methods=["POST"]) @api_web_bp.route("/justificatif/<int:justif_id>/edit", methods=["POST"])
@login_required @login_required
@ -235,9 +224,12 @@ def justif_edit(justif_id: int):
""" """
Edition d'un justificatif à partir de son id Edition d'un justificatif à partir de son id
La requête doit avoir un content type "application/json": La requête doit avoir un content type "application/json":
{ {
"etat"?: str, "etat"?: str,
"raison"?: str "raison"?: str
"date_debut"?: str
"date_fin"?: str
} }
""" """
justificatif_unique: Justificatif = Justificatif.query.filter_by( justificatif_unique: Justificatif = Justificatif.query.filter_by(
@ -250,17 +242,58 @@ def justif_edit(justif_id: int):
# Cas 1 : Etat # Cas 1 : Etat
if data.get("etat") is not None: if data.get("etat") is not None:
data = _change_etat(data, False) etat = scu.EtatJustificatif.get(data.get("etat"))
if data.get("etat") is None: if etat is None:
errors.append("param 'etat': invalide") errors.append("param 'etat': invalide")
else: else:
justificatif_unique.etat = data.get("etat") justificatif_unique.etat = etat
# Cas 2 : raison # Cas 2 : raison
raison = data.get("raison", False) raison = data.get("raison", False)
if raison is not False: if raison is not False:
justificatif_unique.raison = raison justificatif_unique.raison = raison
deb, fin = None, None
# cas 3 : date_debut
date_debut = data.get("date_debut", False)
if date_debut is not False:
if date_debut is None:
errors.append("param 'date_debut': manquant")
deb = scu.is_iso_formated(date_debut, convert=True)
if deb is None:
errors.append("param 'date_debut': format invalide")
if justificatif_unique.date_fin >= deb:
errors.append("param 'date_debut': date de début située après date de fin ")
# cas 4 : date_fin
date_fin = data.get("date_fin", False)
if date_fin is not False:
if date_fin is None:
errors.append("param 'date_fin': manquant")
fin = scu.is_iso_formated(date_fin, convert=True)
if fin is None:
errors.append("param 'date_fin': format invalide")
if justificatif_unique.date_debut <= fin:
errors.append("param 'date_fin': date de fin située avant date de début ")
# Vérification du conflit d'horaire
if (deb is not None) or (fin is not None):
deb = deb if deb is not None else justificatif_unique.date_debut
fin = fin if fin is not None else justificatif_unique.date_fin
justificatifs_list: list[Justificatif] = Justificatif.query.filter_by(
etuid=justificatif_unique.etudid
).all()
if is_period_conflicting(deb, fin, justificatifs_list):
errors.append(
"Modification de la plage horaire impossible: conflit avec les autres justificatifs"
)
justificatif_unique.date_debut = deb
justificatif_unique.date_fin = fin
if errors: if errors:
err: str = ", ".join(errors) err: str = ", ".join(errors)
return json_error(404, err) return json_error(404, err)
@ -270,7 +303,6 @@ def justif_edit(justif_id: int):
return jsonify({"OK": True}) return jsonify({"OK": True})
# TODO: justificatif-delete
@bp.route("/justificatif/delete", methods=["POST"]) @bp.route("/justificatif/delete", methods=["POST"])
@api_web_bp.route("/justificatif/delete", methods=["POST"]) @api_web_bp.route("/justificatif/delete", methods=["POST"])
@login_required @login_required
@ -312,12 +344,18 @@ def _delete_singular(justif_id: int, database):
).first() ).first()
if justificatif_unique is None: if justificatif_unique is None:
return (404, "Justificatif non existant") return (404, "Justificatif non existant")
archive_name: str = justificatif_unique.fichier
if archive_name is not None:
archiver: JustificatifArchiver = JustificatifArchiver()
archiver.delete_justificatif(justificatif_unique.etudid, archive_name)
database.session.delete(justificatif_unique) database.session.delete(justificatif_unique)
return (200, "OK") return (200, "OK")
# Partie archivage # Partie archivage
# TODO: justificatif-import
@bp.route("/justificatif/import/<int:justif_id>", methods=["POST"]) @bp.route("/justificatif/import/<int:justif_id>", methods=["POST"])
@api_web_bp.route("/justificatif/import/<int:justif_id>", methods=["POST"]) @api_web_bp.route("/justificatif/import/<int:justif_id>", methods=["POST"])
@scodoc @scodoc
@ -362,7 +400,6 @@ def justif_import(justif_id: int = None):
return json_error(404, err.args[1]) return json_error(404, err.args[1])
# TODO: justificatif-export
@bp.route("/justificatif/export/<int:justif_id>/<filename>", methods=["GET"]) @bp.route("/justificatif/export/<int:justif_id>/<filename>", methods=["GET"])
@api_web_bp.route("/justificatif/export/<int:justif_id>/<filename>", methods=["GET"]) @api_web_bp.route("/justificatif/export/<int:justif_id>/<filename>", methods=["GET"])
@scodoc @scodoc
@ -394,7 +431,6 @@ def justif_export(justif_id: int = None, filename: str = None):
return json_error(404, err.args[1]) return json_error(404, err.args[1])
# TODO: justificatif-remove
@bp.route("/justificatif/remove/<int:justif_id>", methods=["POST"]) @bp.route("/justificatif/remove/<int:justif_id>", methods=["POST"])
@api_web_bp.route("/justificatif/remove/<int:justif_id>", methods=["POST"]) @api_web_bp.route("/justificatif/remove/<int:justif_id>", methods=["POST"])
@scodoc @scodoc
@ -404,7 +440,7 @@ def justif_export(justif_id: int = None, filename: str = None):
def justif_remove(justif_id: int = None): def justif_remove(justif_id: int = None):
""" """
Supression d'un fichier ou d'une archive Supression d'un fichier ou d'une archive
# TOTALK: Doc, expliquer les noms coté server
{ {
"remove": <"all"/"list"> "remove": <"all"/"list">
@ -459,7 +495,6 @@ def justif_remove(justif_id: int = None):
return jsonify({"response": "removed"}) return jsonify({"response": "removed"})
# TODO: justificatif-list
@bp.route("/justificatif/list/<int:justif_id>", methods=["GET"]) @bp.route("/justificatif/list/<int:justif_id>", methods=["GET"])
@api_web_bp.route("/justificatif/list/<int:justif_id>", methods=["GET"]) @api_web_bp.route("/justificatif/list/<int:justif_id>", methods=["GET"])
@scodoc @scodoc
@ -492,16 +527,29 @@ def justif_list(justif_id: int = None):
# Partie justification # Partie justification
# TODO: justificatif-justified # TODO: justificatif-justified
@bp.route("/justificatif/justified/<int:justif_id>", methods=["GET"])
@api_web_bp.route("/justificatif/justified/<int:justif_id>", methods=["GET"])
@scodoc
@login_required
@permission_required(Permission.ScoView)
# @permission_required(Permission.ScoAssiduiteChange)
def justif_justified(justif_id: int = None):
"""
Liste assiduite_id justifiées par le justificatif
"""
query = Justificatif.query.filter_by(id=justif_id)
if g.scodoc_dept:
query = query.join(Identite).filter_by(dept_id=g.scodoc_dept_id)
justificatif_unique: Justificatif = query.first_or_404()
assiduites_list: list[int] = scass.justifies(justificatif_unique)
return jsonify(assiduites_list)
# -- Utils -- # -- Utils --
def _change_etat(data: dict, from_int: bool = True):
"""change dans un json la valeur du champs état"""
if from_int:
data["etat"] = scu.ETAT_JUSTIFICATIF_NAME.get(data["etat"])
else:
data["etat"] = scu.ETATS_JUSTIFICATIF.get(data["etat"])
return data
def _filter_manager(requested, justificatifs_query): def _filter_manager(requested, justificatifs_query):

View File

@ -51,14 +51,18 @@ 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) -> dict: def to_dict(self, format_api=True) -> dict:
etat = self.etat
if format_api:
etat = EtatJustificatif.inverse().get(self.etat).name
data = { data = {
"assiduite_id": self.assiduite_id, "assiduite_id": self.assiduite_id,
"etudid": self.etudid, "etudid": self.etudid,
"moduleimpl_id": self.moduleimpl_id, "moduleimpl_id": self.moduleimpl_id,
"date_debut": self.date_debut, "date_debut": self.date_debut,
"date_fin": self.date_fin, "date_fin": self.date_fin,
"etat": self.etat, "etat": etat,
"desc": self.desc, "desc": self.desc,
"entry_date": self.entry_date, "entry_date": self.entry_date,
} }
@ -78,17 +82,8 @@ class Assiduite(db.Model):
# Vérification de non duplication des périodes # Vérification de non duplication des périodes
assiduites: list[Assiduite] = etud.assiduites.all() assiduites: list[Assiduite] = etud.assiduites.all()
date_debut = localize_datetime(date_debut) assiduites: list[Justificatif] = etud.assiduites.all()
date_fin = localize_datetime(date_fin) if is_period_conflicting(date_debut, date_fin, assiduites) != 0:
assiduites = [
ass
for ass in assiduites
if is_period_overlapping(
(date_debut, date_fin),
(ass.date_debut, ass.date_fin),
)
]
if len(assiduites) != 0:
raise ScoValueError( raise ScoValueError(
"Duplication des assiduités (la période rentrée rentre en conflit avec une assiduité enregistrée)" "Duplication des assiduités (la période rentrée rentre en conflit avec une assiduité enregistrée)"
) )
@ -156,13 +151,20 @@ class Justificatif(db.Model):
# Archive_id -> sco_archives_justificatifs.py # Archive_id -> sco_archives_justificatifs.py
fichier = db.Column(db.Text()) fichier = db.Column(db.Text())
def to_dict(self) -> dict: def to_dict(self, format_api: bool = False) -> dict:
"""transformation de l'objet en dictionnaire sérialisable"""
etat = self.etat
if format_api:
etat = EtatJustificatif.inverse().get(self.etat).name
data = { data = {
"justif_id": self.justif_id, "justif_id": self.justif_id,
"etudid": self.etudid, "etudid": self.etudid,
"date_debut": self.date_debut, "date_debut": self.date_debut,
"date_fin": self.date_fin, "date_fin": self.date_fin,
"etat": self.etat, "etat": etat,
"raison": self.raison, "raison": self.raison,
"fichier": self.fichier, "fichier": self.fichier,
"entry_date": self.entry_date, "entry_date": self.entry_date,
@ -181,23 +183,12 @@ class Justificatif(db.Model):
"""Créer un nouveau justificatif pour l'étudiant""" """Créer un nouveau justificatif pour l'étudiant"""
# Vérification de non duplication des périodes # Vérification de non duplication des périodes
justificatifs: list[Justificatif] = etud.justificatifs.all() justificatifs: list[Justificatif] = etud.justificatifs.all()
if is_period_conflicting(date_debut, date_fin, justificatifs) != 0:
date_debut = localize_datetime(date_debut)
date_fin = localize_datetime(date_fin)
justificatifs = [
just
for just in justificatifs
if is_period_overlapping(
(date_debut, date_fin),
(just.date_debut, just.date_fin),
)
]
if len(justificatifs) != 0:
raise ScoValueError( raise ScoValueError(
"Duplication des justificatifs (la période rentrée rentre en conflit avec un justificatif enregistré)" "Duplication des justificatifs (la période rentrée rentre en conflit avec un justificatif enregistré)"
) )
nouv_assiduite = Justificatif( nouv_justificatif = Justificatif(
date_debut=date_debut, date_debut=date_debut,
date_fin=date_fin, date_fin=date_fin,
etat=etat, etat=etat,
@ -205,4 +196,28 @@ class Justificatif(db.Model):
raison=raison, raison=raison,
) )
return nouv_assiduite return nouv_justificatif
def is_period_conflicting(
date_debut: datetime,
date_fin: datetime,
collection: list[Assiduite or Justificatif],
) -> bool:
"""
Vérifie si une date n'entre pas en collision
avec les justificatifs ou assiduites déjà présentes
"""
date_debut = localize_datetime(date_debut)
date_fin = localize_datetime(date_fin)
unified = [
uni
for uni in collection
if is_period_overlapping(
(date_debut, date_fin),
(uni.date_debut, uni.date_fin),
)
]
return len(unified) != 0

View File

@ -68,7 +68,7 @@ from app import log
from app.but import jury_but_pv from app.but import jury_but_pv
from app.comp import res_sem from app.comp import res_sem
from app.comp.res_compat import NotesTableCompat from app.comp.res_compat import NotesTableCompat
from app.models import Departement, FormSemestre from app.models import FormSemestre
from app.scodoc.TrivialFormulator import TrivialFormulator from app.scodoc.TrivialFormulator import TrivialFormulator
from app.scodoc.sco_exceptions import ( from app.scodoc.sco_exceptions import (
AccessDenied, AccessDenied,
@ -89,6 +89,11 @@ class BaseArchiver(object):
self.archive_type = archive_type self.archive_type = archive_type
self.initialized = False self.initialized = False
self.root = None self.root = None
self.dept_id = None
def set_dept_id(self, dept_id: int):
"set dept"
self.dept_id = dept_id
def initialize(self): def initialize(self):
if self.initialized: if self.initialized:
@ -110,6 +115,8 @@ class BaseArchiver(object):
finally: finally:
scu.GSL.release() scu.GSL.release()
self.initialized = True self.initialized = True
if self.dept_id is None:
self.dept_id = getattr(g, "scodoc_dept_id")
def get_obj_dir(self, oid: int): def get_obj_dir(self, oid: int):
""" """
@ -117,8 +124,7 @@ class BaseArchiver(object):
If directory does not yet exist, create it. If directory does not yet exist, create it.
""" """
self.initialize() self.initialize()
dept = Departement.query.filter_by(acronym=g.scodoc_dept).first() dept_dir = os.path.join(self.root, str(self.dept_id))
dept_dir = os.path.join(self.root, str(dept.id))
try: try:
scu.GSL.acquire() scu.GSL.acquire()
if not os.path.isdir(dept_dir): if not os.path.isdir(dept_dir):
@ -137,8 +143,7 @@ class BaseArchiver(object):
:return: list of archive oids :return: list of archive oids
""" """
self.initialize() self.initialize()
dept = Departement.query.filter_by(acronym=g.scodoc_dept).first() base = os.path.join(self.root, str(self.dept_id)) + os.path.sep
base = os.path.join(self.root, str(dept.id)) + os.path.sep
dirs = glob.glob(base + "*") dirs = glob.glob(base + "*")
return [os.path.split(x)[1] for x in dirs] return [os.path.split(x)[1] for x in dirs]

View File

@ -19,9 +19,6 @@ class JustificatifArchiver(BaseArchiver):
[_description.txt] [_description.txt]
[<filename.ext>] [<filename.ext>]
TODO:
- Faire fonction suppression fichier unique dans archive
""" """
def __init__(self): def __init__(self):
@ -38,6 +35,7 @@ class JustificatifArchiver(BaseArchiver):
""" """
Ajoute un fichier dans une archive "justificatif" pour l'etudid donné Ajoute un fichier dans une archive "justificatif" pour l'etudid donné
Retourne l'archive_name utilisé Retourne l'archive_name utilisé
TODO: renvoie archive_name + filename
""" """
self._set_dept(etudid) self._set_dept(etudid)
if archive_name is None: if archive_name is None:
@ -104,9 +102,8 @@ class JustificatifArchiver(BaseArchiver):
) )
def _set_dept(self, etudid: int): def _set_dept(self, etudid: int):
if g.scodoc_dept is None or g.scodoc_dept_id is None: """
Mets à jour le dept_id de l'archiver en fonction du département de l'étudiant
"""
etud: Identite = Identite.query.filter_by(id=etudid).first() etud: Identite = Identite.query.filter_by(id=etudid).first()
dept: Departement = Departement.query.filter_by(id=etud.dept_id).first() self.set_dept_id(etud.dept_id)
g.scodoc_dept = dept.acronym
g.scodoc_dept_id = dept.id

View File

@ -85,7 +85,7 @@ def filter_assiduites_by_etat(assiduites: Assiduite, etat: str) -> Assiduite:
Filtrage d'une collection d'assiduites en fonction de leur état Filtrage d'une collection d'assiduites en fonction de leur état
""" """
etats: list[str] = list(etat.split(",")) etats: list[str] = list(etat.split(","))
etats = [scu.ETATS_ASSIDUITE.get(e, -1) for e in etats] etats = [scu.EtatAssiduite.get(e, -1) for e in etats]
return assiduites.filter(Assiduite.etat.in_(etats)) return assiduites.filter(Assiduite.etat.in_(etats))
@ -117,7 +117,7 @@ def filter_justificatifs_by_etat(
Filtrage d'une collection de justificatifs en fonction de leur état Filtrage d'une collection de justificatifs en fonction de leur état
""" """
etats: list[str] = list(etat.split(",")) etats: list[str] = list(etat.split(","))
etats = [scu.ETATS_JUSTIFICATIF.get(e, -1) for e in etats] etats = [scu.EtatJustificatif.get(e, -1) for e in etats]
return justificatifs.filter(Justificatif.etat.in_(etats)) return justificatifs.filter(Justificatif.etat.in_(etats))
@ -172,3 +172,27 @@ def filter_by_formsemestre(assiduites_query: Assiduite, formsemestre: FormSemest
Assiduite.date_debut >= formsemestre.date_debut Assiduite.date_debut >= formsemestre.date_debut
) )
return assiduites_query.filter(Assiduite.date_fin <= formsemestre.date_fin) return assiduites_query.filter(Assiduite.date_fin <= formsemestre.date_fin)
def justifies(justi: Justificatif) -> list[int]:
"""
Retourne la liste des assiduite_id qui sont justifié par la justification
Une assiduité est justifiée si elle est STRICTEMENT comprise dans la plage du justificatif
et que l'état du justificatif est "validé"
"""
justified: list[int] = []
if justi.etat != scu.EtatJustificatif.VALIDE:
return justified
assiduites_query: Assiduite = (
Assiduite.query.join(Justificatif)
.filter_by(etudid=justi.etudid)
.filter(justi.date_debut >= Assiduite.date_debut)
.filter(justi.date_fin <= Assiduite.date_fin)
)
justified = [assi.id for assi in assiduites_query.all()]
return justified

View File

@ -32,7 +32,7 @@ import base64
import bisect import bisect
import copy import copy
import datetime import datetime
from enum import IntEnum from enum import IntEnum, Enum
import io import io
import json import json
from hashlib import md5 from hashlib import md5
@ -50,17 +50,17 @@ from PIL import Image as PILImage
import pydot import pydot
import requests import requests
import dateutil.parser as dtparser
import flask import flask
from flask import g, request from flask import g, request
from flask import flash, url_for, make_response, jsonify 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 from app import log, db
from app.scodoc.sco_vdi import ApoEtapeVDI from app.scodoc.sco_vdi import ApoEtapeVDI
from app.scodoc.sco_codes_parcours import NOTES_TOLERANCE, CODES_EXPL from app.scodoc.sco_codes_parcours import NOTES_TOLERANCE, CODES_EXPL
from app.scodoc import sco_xml from app.scodoc import sco_xml
from app.scodoc.intervals import intervalmap
import sco_version import sco_version
@ -88,7 +88,43 @@ ETATS_INSCRIPTION = {
} }
class EtatAssiduite(IntEnum): def get_model_api_object(model_cls: db.Model, model_id: int):
from app.models import Identite
query = model_cls.query.filter_by(id=model_id)
if g.scodoc_dept:
query = query.join(Identite).filter_by(dept_id=g.scodoc_dept_id)
unique: model_cls = query.first_or_404()
return jsonify(unique.to_dict(format_api=True))
class BiDirectionalEnum(Enum):
"""Permet la recherche inverse d'un enum
Condition : les clés et les valeurs doivent être uniques
les clés doivent être en MAJUSCULES
"""
@classmethod
def contains(cls, attr: str):
return attr.upper() in cls._member_names_
@classmethod
def get(cls, attr: str, default: any = None):
val = None
try:
val = cls[attr.upper()]
except (KeyError, AttributeError):
val = default
return val
@classmethod
def inverse(cls):
"""Retourne un dictionnaire représentant la map inverse de l'Enum"""
return cls._value2member_map_
class EtatAssiduite(int, BiDirectionalEnum):
"""Code des états d'assiduité""" """Code des états d'assiduité"""
# Stockés en BD ne pas modifier # Stockés en BD ne pas modifier
@ -110,7 +146,7 @@ ETATS_ASSIDUITE = {
} }
class EtatJustificatif(IntEnum): class EtatJustificatif(int, BiDirectionalEnum):
"""Code des états des justificatifs""" """Code des états des justificatifs"""
# Stockés en BD ne pas modifier # Stockés en BD ne pas modifier
@ -121,21 +157,6 @@ class EtatJustificatif(IntEnum):
MODIFIE = 3 MODIFIE = 3
ETAT_JUSTIFICATIF_NAME = {
EtatJustificatif.VALIDE: "validé",
EtatJustificatif.NON_VALIDE: "non validé",
EtatJustificatif.ATTENTE: "en attente",
EtatJustificatif.MODIFIE: "modifié",
}
ETATS_JUSTIFICATIF = {
"validé": EtatJustificatif.VALIDE,
"non vaidé": EtatJustificatif.NON_VALIDE,
"en attente": EtatJustificatif.ATTENTE,
"modifié": EtatJustificatif.MODIFIE,
}
def is_iso_formated(date: str, convert=False) -> bool or datetime.datetime or None: def is_iso_formated(date: str, convert=False) -> bool or datetime.datetime or None:
""" """
Vérifie si une date est au format iso Vérifie si une date est au format iso
@ -147,7 +168,6 @@ def is_iso_formated(date: str, convert=False) -> bool or datetime.datetime or No
Retourne None sinon Retourne None sinon
""" """
import dateutil.parser as dtparser
try: try:
date: datetime.datetime = dtparser.isoparse(date) date: datetime.datetime = dtparser.isoparse(date)

View File

@ -1,5 +1,5 @@
""" """
Test de l'api Assiduité Test de l'api justificatif
Ecrit par HARTMANN Matthias Ecrit par HARTMANN Matthias
@ -121,7 +121,7 @@ def test_route_create(api_headers):
# -== Unique ==- # -== Unique ==-
# Bon fonctionnement # Bon fonctionnement
data = create_data("validé", "01") data = create_data("valide", "01")
res = POST_JSON(f"/justificatif/{ETUDID}/create", [data], api_headers) res = POST_JSON(f"/justificatif/{ETUDID}/create", [data], api_headers)
check_fields(res, BATCH_FIELD) check_fields(res, BATCH_FIELD)
@ -129,7 +129,7 @@ def test_route_create(api_headers):
TO_REMOVE.append(res["success"]["0"]["justif_id"]) TO_REMOVE.append(res["success"]["0"]["justif_id"])
data2 = create_data("modifié", "02", "raison") data2 = create_data("modifie", "02", "raison")
res = POST_JSON(f"/justificatif/{ETUDID}/create", [data2], api_headers) res = POST_JSON(f"/justificatif/{ETUDID}/create", [data2], api_headers)
check_fields(res, BATCH_FIELD) check_fields(res, BATCH_FIELD)
assert len(res["success"]) == 1 assert len(res["success"]) == 1
@ -160,7 +160,7 @@ def test_route_create(api_headers):
# Bon Fonctionnement # Bon Fonctionnement
etats = ["validé", "modifé", "non validé", "en attente"] etats = ["valide", "modifie", "non_valide", "attente"]
data = [ data = [
create_data(etats[d % 4], 10 + d, "raison" if d % 2 else None) create_data(etats[d % 4], 10 + d, "raison" if d % 2 else None)
for d in range(randint(3, 5)) for d in range(randint(3, 5))
@ -175,10 +175,10 @@ def test_route_create(api_headers):
# Mauvais Fonctionnement # Mauvais Fonctionnement
data2 = [ data2 = [
create_data("modifié", "01"), create_data("modifie", "01"),
create_data(None, "25"), create_data(None, "25"),
create_data("blabla", 26), create_data("blabla", 26),
create_data("validé", 32), create_data("valide", 32),
] ]
res = POST_JSON(f"/justificatif/{ETUDID}/create", data2, api_headers) res = POST_JSON(f"/justificatif/{ETUDID}/create", data2, api_headers)
@ -201,7 +201,7 @@ def test_route_edit(api_headers):
# Bon fonctionnement # Bon fonctionnement
data = {"etat": "modifié", "raison": "test"} data = {"etat": "modifie", "raison": "test"}
res = POST_JSON(f"/justificatif/{TO_REMOVE[0]}/edit", data, api_headers) res = POST_JSON(f"/justificatif/{TO_REMOVE[0]}/edit", data, api_headers)
assert res == {"OK": True} assert res == {"OK": True}
@ -305,12 +305,12 @@ def test_import_justificatif(api_headers):
# Bon fonctionnement # Bon fonctionnement
filename: str = "/opt/scodoc/tests/api/test_api_justificatif.txt" filename: str = "tests/api/test_api_justificatif.txt"
resp: dict = send_file(1, filename, api_headers) resp: dict = send_file(1, filename, api_headers)
assert "response" in resp assert "response" in resp
assert resp["response"] == "imported" assert resp["response"] == "imported"
filename: str = "/opt/scodoc/tests/api/test_api_justificatif2.txt" filename: str = "tests/api/test_api_justificatif2.txt"
resp: dict = send_file(1, filename, api_headers) resp: dict = send_file(1, filename, api_headers)
assert "response" in resp assert "response" in resp
assert resp["response"] == "imported" assert resp["response"] == "imported"
@ -372,3 +372,4 @@ def test_remove_justificatif(api_headers):
check_failure_post("/justificatif/remove/2", api_headers, {}) check_failure_post("/justificatif/remove/2", api_headers, {})
check_failure_post(f"/justificatif/remove/{FAUX}", api_headers, {"remove": "all"}) check_failure_post(f"/justificatif/remove/{FAUX}", api_headers, {"remove": "all"})
check_failure_post("/justificatif/remove/1", api_headers, {"remove": "all"})

View File

@ -14,7 +14,7 @@ from app import db
from app.scodoc import sco_formsemestre from app.scodoc import sco_formsemestre
import app.scodoc.sco_assiduites as scass import app.scodoc.sco_assiduites as scass
from app.models import Assiduite, Identite, FormSemestre, ModuleImpl from app.models import Assiduite, Justificatif, Identite, FormSemestre, ModuleImpl
from app.scodoc.sco_exceptions import ScoValueError from app.scodoc.sco_exceptions import ScoValueError
import app.scodoc.sco_utils as scu import app.scodoc.sco_utils as scu
@ -118,9 +118,61 @@ def test_general(test_client):
verifier_comptage_et_filtrage( verifier_comptage_et_filtrage(
etuds, moduleimpls, (formsemestre_1, formsemestre_2, formsemestre_3) etuds, moduleimpls, (formsemestre_1, formsemestre_2, formsemestre_3)
) )
editer_supprimer_assiduiter(etuds, moduleimpls) editer_supprimer_assiduiter(etuds, moduleimpls)
def ajouter_justificatifs(etud, etud_faux):
obj_justificatifs = [
{
"etat": scu.EtatJustificatif.ATTENTE,
"deb": "2022-09-03T08:00+01:00",
"fin": "2022-09-03T10:00+01:00",
"raison": None,
},
{
"etat": scu.EtatJustificatif.VALIDE,
"deb": "2023-01-03T07:00+01:00",
"fin": "2023-01-03T11:00+01:00",
"raison": None,
},
{
"etat": scu.EtatJustificatif.VALIDE,
"deb": "2022-09-03T09:00:01+01:00",
"fin": "2022-09-03T12:00+01:00",
"raison": None,
},
{
"etat": scu.EtatJustificatif.NON_VALIDE,
"deb": "2022-09-03T14:00:00+01:00",
"fin": "2022-09-03T15:00+01:00",
"raison": "Description",
},
{
"etat": scu.EtatJustificatif.MODIFIE,
"deb": "2023-01-03T11:00:01+01:00",
"fin": "2023-01-03T12:00+01:00",
"raison": None,
},
]
justificatifs = [
Justificatif.create_justificatif(
etud,
ass["deb"],
ass["fin"],
ass["etat"],
ass["raison"],
)
for ass in obj_justificatifs
]
# Vérification de la création des justificatifs
assert [
justi for justi in justificatifs if not isinstance(justi, Justificatif)
] == [], "La création des justificatifs de base n'est pas OK"
def editer_supprimer_assiduiter(etuds: list[Identite], moduleimpls: list[int]): def editer_supprimer_assiduiter(etuds: list[Identite], moduleimpls: list[int]):
""" """
Troisième Partie: Troisième Partie: