API Assiduites :

- Comptage
- Filtrage par Formsemestre
- Envoi en lot (create / delete)
This commit is contained in:
iziram 2022-12-14 17:41:57 +01:00
parent 0a1a847044
commit 3ff4abd19c
4 changed files with 526 additions and 134 deletions

View File

@ -6,25 +6,33 @@
"""ScoDoc 9 API : Assiduités
"""
from datetime import datetime
from pytz import UTC
from typing import List
from flask import g, jsonify, request
from app import db
from app.api import api_bp as bp
from app.api import api_bp as bp, api_web_bp
from app.scodoc.sco_utils import json_error
from app.decorators import scodoc, permission_required
from app.scodoc.sco_permissions import Permission
from flask_login import login_required
from app.models import Identite, Assiduite
from app.models import Identite, Assiduite, FormSemestreInscription, FormSemestre
import app.scodoc.sco_utils as scu
import app.scodoc.sco_assiduites as scass
"""
TODO:
- Faire une nouvelle route pour obtenir toutes les assiduites d'un formsemstre
- Ajouter des variantes "batch" pour chaque route POST pour envoyer des listes d'objets
"""
@bp.route("/assiduite/<int:assiduiteid>")
@api_web_bp.route("/assiduite/<int:assiduiteid>")
@scodoc
@permission_required(Permission.ScoView)
def assiduite(assiduiteid: int = None):
@ -33,10 +41,10 @@ def assiduite(assiduiteid: int = None):
Exemple de résultat:
{
"assiduiteid": 1,
"etuid": 2,
"etudid": 2,
"moduleimpl_id": 3,
"date_debut": "2022-10-31T08:00",
"date_fin": "2022-10-31T10:00",
"date_debut": "2022-10-31T08:00+01:00",
"date_fin": "2022-10-31T10:00+01:00",
"etat": "retard"
}
"""
@ -50,67 +58,223 @@ def assiduite(assiduiteid: int = None):
return jsonify(change_etat(data))
@bp.route("/assiduites/<int:etuid>", defaults={"with_query": False})
@bp.route("/assiduites/<int:etuid>/query", defaults={"with_query": True})
@bp.route("/assiduites/<int:etudid>/count", defaults={"with_query": False})
@bp.route("/assiduites/<int:etudid>/count/query", defaults={"with_query": True})
@api_web_bp.route("/assiduites/<int:etudid>/count", defaults={"with_query": False})
@api_web_bp.route("/assiduites/<int:etudid>/count/query", defaults={"with_query": True})
@login_required
@scodoc
@permission_required(Permission.ScoView)
def assiduites(etuid: int = None, with_query: bool = False):
"""Retourne toutes les assiduités d'un étudiant"""
query = Identite.query.filter_by(id=etuid)
def count_assiduites(etudid: int = None, with_query: bool = False):
"""
Retourne le nombre d'assiduités d'un étudiant
chemin : /assiduites/<int:etudid>/count
Un filtrage peut être donné avec une query
chemin : /assiduites/<int:etudid>/count/query?
Les différents filtres :
Type (type de comptage -> journee, demi, heure, nombre d'assiduite):
query?type=(journee, demi, heure) -> une seule valeur parmis les trois
ex: .../query?type=heure
Comportement par défaut : compte le nombre d'assiduité enregistrée
Etat (etat de l'étudiant -> absent, present ou retard):
query?etat=[- liste des états séparé par une virgule -]
ex: .../query?etat=present,retard
Date debut
(date de début de l'assiduité, sont affichés les assiduités
dont la date de début est supérieur ou égale à la valeur donnée):
query?date_debut=[- date au format iso -]
ex: query?date_debut=2022-11-03T08:00+01:00
Date fin
(date de fin de l'assiduité, sont affichés les assiduités
dont la date de fin est inférieure ou égale à la valeur donnée):
query?date_fin=[- date au format iso -]
ex: query?date_fin=2022-11-03T10:00+01:00
Moduleimpl_id (l'id du module concerné par l'assiduité):
query?moduleimpl_id=[- int ou vide -]
ex: query?moduleimpl_id=1234
query?moduleimpl_od=
Formsemstre_id (l'id du formsemestre concerné par l'assiduité)
query?formsemstre_id=[int]
ex query?formsemestre_id=3
"""
query = Identite.query.filter_by(id=etudid)
if g.scodoc_dept:
query = query.filter_by(dept_id=g.scodoc_dept_id)
etud: Identite = query.first_or_404(etuid)
assiduites: List[Assiduite] = etud.assiduites.all()
etud: Identite = query.first_or_404(etudid)
filter: dict[str, object] = {}
metric: str = "all"
if with_query:
# cas 1 : etat assiduite
etat = request.args.get("etat")
if etat is not None:
etat = list(etat.split(","))
etat = [scu.ETATS_ASSIDUITE.get(e, "absent") for e in etat]
assiduites = [ass for ass in assiduites if ass.etat in etat]
filter["etat"] = etat
# cas 2 : date de début
deb = request.args.get("date_debut")
deb: datetime = is_iso_formated(deb, True)
deb: datetime = scu.is_iso_formated(deb, True)
if deb is not None:
filtered_assiduites = []
for ass in assiduites:
if deb.tzinfo is None:
deb: datetime = deb.replace(tzinfo=ass.date_debut.tzinfo)
if ass.date_debut >= deb:
filtered_assiduites.append(ass)
assiduites.clear()
assiduites.extend(filtered_assiduites)
filter["date_debut"] = deb
# cas 3 : date de fin
fin = request.args.get("date_fin")
fin = is_iso_formated(fin, True)
fin = scu.is_iso_formated(fin, True)
if fin is not None:
filtered_assiduites = []
for ass in assiduites:
if fin.tzinfo is None:
fin: datetime = fin.replace(tzinfo=ass.date_fin.tzinfo)
if ass.date_fin <= fin:
filtered_assiduites.append(ass)
assiduites.clear()
assiduites.extend(filtered_assiduites)
filter["date_fin"] = fin
# cas 4 : moduleimpl_id
module = request.args.get("moduleimpl_id")
module = request.args.get("moduleimpl_id", False)
try:
if module is False:
raise Exception
if module != "":
module = int(module)
except Exception:
else:
module = None
except Exception:
module = False
if module is not None:
assiduites = [ass for ass in assiduites if ass.moduleimpl_id == module]
if module is not False:
filter["moduleimpl_id"] = module
# cas 5 : formsemestre_id
formsemestre_id = request.args.get("formsemestre_id")
if formsemestre_id is not None:
formsemestre: FormSemestre = None
formsemestre_id = int(formsemestre_id)
formsemestre = FormSemestre.query.filter_by(id=formsemestre_id).first()
filter["formsemestre"] = formsemestre
# cas 6 : type
metric = request.args.get("metric", "count")
return jsonify(scass.get_assiduite_stats(etud=etud, metric=metric, filter=filter))
@bp.route("/assiduites/<int:etudid>", defaults={"with_query": False})
@bp.route("/assiduites/<int:etudid>/query", defaults={"with_query": True})
@api_web_bp.route("/assiduites/<int:etudid>", defaults={"with_query": False})
@api_web_bp.route("/assiduites/<int:etudid>/query", defaults={"with_query": True})
@login_required
@scodoc
@permission_required(Permission.ScoView)
def assiduites(etudid: int = None, with_query: bool = False):
"""
Retourne toutes les assiduités d'un étudiant
chemin : /assiduites/<int:etudid>
Un filtrage peut être donné avec une query
chemin : /assiduites/<int:etudid>/query?
Les différents filtres :
Etat (etat de l'étudiant -> absent, present ou retard):
query?etat=[- liste des états séparé par une virgule -]
ex: .../query?etat=present,retard
Date debut
(date de début de l'assiduité, sont affichés les assiduités
dont la date de début est supérieur ou égale à la valeur donnée):
query?date_debut=[- date au format iso -]
ex: query?date_debut=2022-11-03T08:00+01:00
Date fin
(date de fin de l'assiduité, sont affichés les assiduités
dont la date de fin est inférieure ou égale à la valeur donnée):
query?date_fin=[- date au format iso -]
ex: query?date_fin=2022-11-03T10:00+01:00
Moduleimpl_id (l'id du module concerné par l'assiduité):
query?moduleimpl_id=[- int ou vide -]
ex: query?moduleimpl_id=1234
query?moduleimpl_od=
"""
query = Identite.query.filter_by(id=etudid)
if g.scodoc_dept:
query = query.filter_by(dept_id=g.scodoc_dept_id)
etud: Identite = query.first_or_404(etudid)
assiduites = etud.assiduites
if with_query:
# cas 1 : etat assiduite
etat = request.args.get("etat")
if etat is not None:
assiduites = scass.filter_by_etat(assiduites, etat)
# cas 2 : date de début
deb = request.args.get("date_debut")
deb: datetime = scu.is_iso_formated(deb, True)
if deb is not None:
assiduites = scass.filter_by_date(assiduites, deb, sup=True)
# cas 3 : date de fin
fin = request.args.get("date_fin")
fin = scu.is_iso_formated(fin, True)
if fin is not None:
assiduites = scass.filter_by_date(assiduites, fin, sup=False)
# cas 4 : moduleimpl_id
module = request.args.get("moduleimpl_id", False)
try:
if module is False:
raise Exception
if module != "":
module = int(module)
else:
module = None
except Exception:
module = False
if module is not False:
assiduites = scass.filter_by_module_impl(assiduites, module)
# cas 5 : formsemestre_id
formsemestre_id = request.args.get("formsemestre_id")
if formsemestre_id is not None:
formsemestre: FormSemestre = None
formsemestre_id = int(formsemestre_id)
formsemestre = FormSemestre.query.filter_by(id=formsemestre_id).first()
assiduites = scass.filter_by_formsemstre(assiduites, formsemestre)
data_set: List[dict] = []
for ass in assiduites.all():
data = ass.to_dict()
data_set.append(change_etat(data))
return jsonify(data_set)
@bp.route("/assiduites/formsemestre/<int:formsemestre_id>")
@api_web_bp.route("/assiduites/formsemestre/<int:formsemestre_id>")
@login_required
@scodoc
@permission_required(Permission.ScoView)
def assiduites_formsemestre(formsemestre_id: int = None):
if formsemestre_id is not None:
formsemestre: FormSemestre = None
formsemestre_id = int(formsemestre_id)
formsemestre = FormSemestre.query.filter_by(id=formsemestre_id).first()
if formsemestre is None:
json_error(404, "le paramètre 'formsemestre_id' n'existe pas")
etuds = formsemestre.etuds.all()
assiduites = []
for etud in etuds:
assiduites.extend(
scass.filter_by_formsemstre(etud.assiduites, formsemestre).all()
)
data_set: List[dict] = []
for ass in assiduites:
@ -119,13 +283,25 @@ def assiduites(etuid: int = None, with_query: bool = False):
return jsonify(data_set)
else:
return json_error(404, "le paramètre 'formsemestre_id doit être renseigné'")
@bp.route("/assiduite/<int:etuid>/create", methods=["POST"])
@bp.route("/assiduite/<int:etudid>/create", methods=["POST"], defaults={"batch": False})
@api_web_bp.route(
"/assiduite/<int:etudid>/create", methods=["POST"], defaults={"batch": False}
)
@bp.route(
"/assiduite/<int:etudid>/create/batch", methods=["POST"], defaults={"batch": True}
)
@api_web_bp.route(
"/assiduite/<int:etudid>/create/batch", methods=["POST"], defaults={"batch": True}
)
@scodoc
@permission_required(Permission.ScoView)
def create(etuid: int = None):
def create(etudid: int = None, batch: bool = False):
"""
Création d'une assiduité pour l'étudiant (etuid)
Création d'une assiduité pour l'étudiant (etudid)
La requête doit avoir un content type "application/json":
{
"date_debut": str,
@ -142,9 +318,33 @@ def create(etuid: int = None):
"""
etud: Identite = Identite.query.filter_by(id=etuid).first_or_404()
etud: Identite = Identite.query.filter_by(id=etudid).first_or_404()
if batch:
errors: dict[int, str] = {}
success: dict[
int,
] = {}
for i, data in enumerate(request.get_json(force=True).get("batch")):
code, obj = create_singular(data, etud)
if code == 404:
errors[i] = obj
else:
success[i] = obj
data = request.get_json(force=True)
return jsonify({"errors": errors, "success": success})
else:
code, obj = create_singular(request.get_json(force=True), etud)
if code == 404:
return json_error(code, obj)
else:
return jsonify(obj)
def create_singular(
data: dict,
etud: Identite,
) -> tuple[int, object]:
errors: List[str] = []
# -- vérifications de l'objet json --
@ -162,7 +362,7 @@ def create(etuid: int = None):
date_debut = data.get("date_debut", None)
if date_debut is None:
errors.append("param 'date_debut': manquant")
deb = is_iso_formated(date_debut, True)
deb = scu.is_iso_formated(date_debut, convert=True)
if deb is None:
errors.append("param 'date_debut': format invalide")
@ -170,7 +370,7 @@ def create(etuid: int = None):
date_fin = data.get("date_fin", None)
if date_fin is None:
errors.append("param 'date_fin': manquant")
fin = is_iso_formated(date_fin, True)
fin = scu.is_iso_formated(date_fin, convert=True)
if fin is None:
errors.append(f"param 'date_fin': format invalide")
@ -187,10 +387,10 @@ def create(etuid: int = None):
if errors != []:
err: str = ", ".join(errors)
return json_error(404, err)
return (404, err)
# TOUT EST OK
nouv_assiduite: Assiduite or str = Assiduite.create_assiduite(
nouv_assiduite: Assiduite or int = Assiduite.create_assiduite(
date_debut=deb,
date_fin=fin,
etat=etat,
@ -199,9 +399,11 @@ def create(etuid: int = None):
)
if type(nouv_assiduite) is Assiduite:
return jsonify({"assiduiteid": nouv_assiduite.assiduiteid})
db.session.add(nouv_assiduite)
db.session.commit()
return (200, {"assiduiteid": nouv_assiduite.assiduiteid})
return json_error(
return (
404,
{
1: "La période sélectionnée est déjà couverte par une autre assiduite",
@ -210,25 +412,74 @@ def create(etuid: int = None):
)
@bp.route("/assiduite/<int:assiduiteid>/delete", methods=["POST"])
@bp.route("/assiduite/delete", methods=["POST"], defaults={"batch": False})
@api_web_bp.route("/assiduite/delete", methods=["POST"], defaults={"batch": False})
@bp.route(
"/assiduite/delete/batch",
methods=["POST"],
defaults={"batch": True},
)
@api_web_bp.route(
"/assiduite/delete/batch",
methods=["POST"],
defaults={"batch": True},
)
@login_required
@scodoc
@permission_required(Permission.ScoAssiduiteChange)
def delete(assiduiteid: int):
def delete(batch: bool = False):
"""
Suppression d'une assiduité à partir de son id
"""
assiduite: Assiduite = Assiduite.query.filter_by(id=assiduiteid).first_or_404()
db.session.delete(assiduite)
output = {}
if batch:
assiduites: list[int] = request.get_json(force=True).get("batch", [])
output = {"errors": [], "success": []}
for ass in assiduites:
code, msg = delete_singular(ass, db)
if code == 404:
output["errors"].append({ass: msg})
else:
output["success"].append({ass: msg})
else:
code, msg = delete_singular(
request.get_json(force=True).get("assiduite_id", -1), db
)
output[code] = msg
db.session.commit()
return jsonify({"OK": True})
return jsonify(output)
@bp.route("/assiduite/<int:assiduiteid>/edit", methods=["POST"])
def delete_singular(assiduite_id: int, db):
assiduite: Assiduite = Assiduite.query.filter_by(id=assiduite_id).first()
if assiduite is None:
return (404, "Assiduite non existante")
db.session.delete(assiduite)
return (200, "OK")
@bp.route(
"/assiduite/<int:assiduiteid>/edit", methods=["POST"], defaults={"batch": False}
)
@api_web_bp.route(
"/assiduite/<int:assiduiteid>/edit", methods=["POST"], defaults={"batch": False}
)
@bp.route(
"/assiduite/<int:assiduiteid>/edit/batch",
methods=["POST"],
defaults={"batch": True},
)
@api_web_bp.route(
"/assiduite/<int:assiduiteid>/edit/batch",
methods=["POST"],
defaults={"batch": True},
)
@login_required
@scodoc
@permission_required(Permission.ScoAssiduiteChange)
def edit(assiduiteid: int):
def edit(assiduiteid: int, batch: bool = False):
"""
Edition d'une assiduité à partir de son id
La requête doit avoir un content type "application/json":
@ -285,25 +536,3 @@ def change_etat(data: dict, from_int: bool = True):
else:
data["etat"] = scu.ETATS_ASSIDUITE.get(data["etat"])
return data
def is_iso_formated(date: str, convert=False) -> bool or datetime or None:
"""
Vérifie si une date est au format iso
Retourne un booléen Vrai (ou un objet Datetime si convert = True)
si l'objet est au format iso
Retourne Faux si l'objet n'est pas au format et convert = False
Retourne None sinon
"""
import dateutil.parser as dtparser
try:
date: datetime = dtparser.isoparse(date)
if date.tzinfo is None:
date = UTC.localize(date)
return date if convert else True
except Exception:
return None if convert else False

View File

@ -2,13 +2,11 @@
"""Gestion de l'assiduité (assiduités + justificatifs)
"""
from app import db
from app.models import ModuleImpl
from app.models import ModuleImpl, ModuleImplInscription
from app.models.etudiants import Identite
from app.models.formsemestre import FormSemestre
from app.scodoc.sco_utils import EtatAssiduite
from app.scodoc.sco_utils import EtatAssiduite, localize_datetime, verif_interval
from datetime import datetime
from typing import Tuple, List
class Assiduite(db.Model):
@ -60,16 +58,22 @@ class Assiduite(db.Model):
date_debut: datetime,
date_fin: datetime,
etat: EtatAssiduite,
module: int or None = None or int,
module: int or None = None,
) -> object or int:
"Créer une nouvelle assiduité pour l'étudiant"
"""Créer une nouvelle assiduité pour l'étudiant
Documentation des codes d'erreurs renvoyés:
1: Duplication des assiduités (la période rentrée rentre en conflit avec une assiduité enregistrée)
2: l'ID du module_impl n'existe pas.
"""
# Vérification de non duplication des périodes
assiduites: List[Assiduite] = etud.assiduites.all()
assiduites: list[Assiduite] = etud.assiduites.all()
assiduites = [
ass
for ass in assiduites
if verif_interval((date_debut, date_fin), (ass.date_debut, ass.date_fin))
if verif_interval(
(localize_datetime(date_debut), localize_datetime(date_fin)),
(ass.date_debut, ass.date_fin),
)
]
if len(assiduites) != 0:
return 1
@ -78,8 +82,8 @@ class Assiduite(db.Model):
# Vérification de l'existance du module pour l'étudiant
if cls.verif_moduleimpl(module, etud):
nouv_assiduite = Assiduite(
date_debut=date_debut.isoformat(),
date_fin=date_fin.isoformat(),
date_debut=date_debut,
date_fin=date_fin,
etat=etat,
etudiant=etud,
moduleimpl_id=module,
@ -93,8 +97,7 @@ class Assiduite(db.Model):
etat=etat,
etudiant=etud,
)
db.session.add(nouv_assiduite)
db.session.commit()
return nouv_assiduite
@staticmethod
@ -104,28 +107,22 @@ class Assiduite(db.Model):
Retourne Vrai si c'est le cas, faux sinon
"""
# -> get obj module impl -> get obj formsemestres -> query etuds avec etuid -> si vide = Error sinon good
output = True
module: ModuleImpl = ModuleImpl.query.filter_by(
moduleimpl_id=moduleimpl_id
).first()
if module is None:
retour = False
output = False
semestre: FormSemestre = FormSemestre.query.filter_by(
id=module.formsemestre_id
).first()
if semestre is None:
retour = False
if output:
search_etudid: int = etud.id if type(etud) == Identite else etud
is_module: int = ModuleImplInscription.query.filter_by(
etudid=search_etudid, moduleimpl_id=moduleimpl_id
).count()
etudiants: List[Identite] = semestre.etuds.all()
output = is_module > 0
if type(etud) is Identite:
retour = etud in etudiants
else:
retour = etud in [e.id for e in etudiants]
return retour
return output
class Justificatif(db.Model):
@ -171,26 +168,3 @@ class Justificatif(db.Model):
"fichier": self.fichier,
}
return data
def verif_interval(periode: Tuple[datetime], interval: Tuple[datetime]) -> bool:
"""
Vérifie si une période est comprise dans un interval, chevauche l'interval ou comprend l'interval
Retourne Vrai si c'est le cas, faux sinon
"""
p_deb, p_fin = periode
i_deb, i_fin = interval
from app.scodoc.intervals import intervalmap
i = intervalmap()
p = intervalmap()
i[:] = 0
p[:] = 0
i[i_deb:i_fin] = 1
p[p_deb:p_fin] = 1
res: int = sum((i[p_deb], i[p_fin], p[i_deb], p[i_fin]))
return res > 0

View File

@ -0,0 +1,130 @@
from app.models.etudiants import Identite
from app.models.formsemestre import FormSemestre
from app.models.assiduites import Assiduite
import app.scodoc.sco_utils as scu
from datetime import datetime, date, time, timedelta
# TOTALK: Réfléchir sur le fractionnement d'une assiduite prolongée
def get_assiduite_stats(
etud: Identite, metric: str = "all", filter: dict[str, object] = {}
) -> Assiduite:
assiduites: Assiduite = etud.assiduites
if filter != {}:
for key in filter:
if key == "etat":
assiduites = filter_by_etat(assiduites, filter[key])
elif key == "date_fin":
assiduites = filter_by_date(assiduites, filter[key], sup=False)
elif key == "date_debut":
assiduites = filter_by_date(assiduites, filter[key], sup=True)
elif key == "moduleimpl_id":
assiduites = filter_by_module_impl(assiduites, filter[key])
elif key == "formsemestre":
assiduites = filter_by_formsemstre(assiduites, filter[key])
count: dict = get_count(assiduites)
metrics: list[str] = metric.split(",")
output: dict = {}
for key in count:
if key in metrics:
output[key] = count[key]
return output if output != {} else count
def get_count(assiduites: Assiduite) -> dict[str, int or float]:
output: dict[str, int or float] = {}
output["compte"] = assiduites.count()
output["heure"] = 0.0
output["journee"] = 0
output["demi"] = 0
all_assiduites: list[Assiduite] = assiduites.order_by(Assiduite.date_debut).all()
current_day: date = None
current_time: str = None
MIDNIGHT: time = time(hour=0)
NOON: time = time(hour=12)
time_check = lambda d: (MIDNIGHT <= d.time() <= NOON)
for ass in all_assiduites:
delta: timedelta = ass.date_fin - ass.date_debut
output["heure"] += delta.total_seconds() / 3600
ass_time: str = time_check(ass.date_debut)
if current_day != ass.date_debut.date():
current_day = ass.date_debut.date()
current_time = ass_time
output["demi"] += 1
output["journee"] += 1
if current_time != ass_time:
current_time = ass_time
output["demi"] += 1
output["heure"] = round(output["heure"], 2)
return output
def filter_by_etat(assiduites: Assiduite, etat: str) -> Assiduite:
"""
Filtrage d'une collection d'assiduites en fonction de leur état
"""
etats: list[str] = list(etat.split(","))
etats = [scu.ETATS_ASSIDUITE.get(e, "") for e in etats]
return assiduites.filter(Assiduite.etat.in_(etats))
def filter_by_date(
assiduites: Assiduite, date: datetime, sup: bool = True
) -> Assiduite:
"""
Filtrage d'une collection d'assiduites en fonction d'une date
Sup == True -> les assiduites doivent débuter après 'date'\n
Sup == False -> les assiduites doivent finir avant 'date'
"""
if date.tzinfo is None:
first_assiduite: Assiduite = assiduites.first()
if first_assiduite is not None:
date: datetime = date.replace(tzinfo=first_assiduite.date_debut.tzinfo)
if sup:
return assiduites.filter(Assiduite.date_debut >= date)
else:
return assiduites.filter(Assiduite.date_fin <= date)
def filter_by_module_impl(
assiduites: Assiduite, module_impl_id: int or None
) -> Assiduite:
"""
Filtrage d'une collection d'assiduites en fonction de l'ID du module_impl
"""
return assiduites.filter(Assiduite.moduleimpl_id == module_impl_id)
def filter_by_formsemstre(assiduites: Assiduite, formsemestre: FormSemestre):
"""
Filtrage d'une collection d'assiduites en fonction d'un formsemestre
"""
if formsemestre is None:
return assiduites.filter(False)
assiduites = assiduites.filter(
Identite.query.filter_by(id=Assiduite.etudid).first()
in formsemestre.etuds.all()
)
assiduites = assiduites.filter(Assiduite.date_debut >= formsemestre.date_debut)
return assiduites.filter(Assiduite.date_fin <= formsemestre.date_fin)

View File

@ -60,6 +60,8 @@ from app import log
from app.scodoc.sco_vdi import ApoEtapeVDI
from app.scodoc.sco_codes_parcours import NOTES_TOLERANCE, CODES_EXPL
from app.scodoc import sco_xml
from app.scodoc.intervals import intervalmap
import sco_version
# le répertoire static, lié à chaque release pour éviter les problèmes de caches
@ -126,6 +128,63 @@ ETAT_JUSTIFICATIF_NAME = {
EtatJustificatif.MODIFIE: "modifié",
}
def is_iso_formated(date: str, convert=False) -> bool or datetime.datetime or None:
"""
Vérifie si une date est au format iso
Retourne un booléen Vrai (ou un objet Datetime si convert = True)
si l'objet est au format iso
Retourne Faux si l'objet n'est pas au format et convert = False
Retourne None sinon
"""
import dateutil.parser as dtparser
try:
date: datetime.datetime = dtparser.isoparse(date)
return date if convert else True
except Exception:
return None if convert else False
def localize_datetime(date: datetime.datetime) -> datetime.datetime:
new_date: datetime.datetime = date
if date.tzinfo == None:
from app.models.assiduites import Assiduite
first_assiduite = Assiduite.query.first()
if first_assiduite is not None:
new_date = date.replace(tzinfo=first_assiduite.date_debut.tzinfo)
return new_date
def verif_interval(
periode: tuple[datetime.datetime], interval: tuple[datetime.datetime]
) -> bool:
"""
Vérifie si une période est comprise dans un interval, chevauche l'interval ou comprend l'interval
Retourne Vrai si c'est le cas, faux sinon
"""
p_deb, p_fin = periode
i_deb, i_fin = interval
i = intervalmap()
p = intervalmap()
i[:] = 0
p[:] = 0
i[i_deb:i_fin] = 1
p[p_deb:p_fin] = 1
res: int = sum((i[p_deb], i[p_fin], p[i_deb], p[i_fin]))
return res > 0
# Types de modules
class ModuleType(IntEnum):
"""Code des types de module."""