From 3ff4abd19c4a1eee7dc11af551e1f66e4aa23cbc Mon Sep 17 00:00:00 2001 From: iziram Date: Wed, 14 Dec 2022 17:41:57 +0100 Subject: [PATCH] API Assiduites : - Comptage - Filtrage par Formsemestre - Envoi en lot (create / delete) --- app/api/assiduites.py | 395 +++++++++++++++++++++++++++-------- app/models/assiduites.py | 76 +++---- app/scodoc/sco_assiduites.py | 130 ++++++++++++ app/scodoc/sco_utils.py | 59 ++++++ 4 files changed, 526 insertions(+), 134 deletions(-) create mode 100644 app/scodoc/sco_assiduites.py diff --git a/app/api/assiduites.py b/app/api/assiduites.py index 9e5951ae..50677b74 100644 --- a/app/api/assiduites.py +++ b/app/api/assiduites.py @@ -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/") +@api_web_bp.route("/assiduite/") @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,82 +58,250 @@ def assiduite(assiduiteid: int = None): return jsonify(change_etat(data)) -@bp.route("/assiduites/", defaults={"with_query": False}) -@bp.route("/assiduites//query", defaults={"with_query": True}) +@bp.route("/assiduites//count", defaults={"with_query": False}) +@bp.route("/assiduites//count/query", defaults={"with_query": True}) +@api_web_bp.route("/assiduites//count", defaults={"with_query": False}) +@api_web_bp.route("/assiduites//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//count + + Un filtrage peut être donné avec une query + chemin : /assiduites//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: - module = int(module) + if module is False: + raise Exception + if module != "": + module = int(module) + else: + module = None except Exception: - module = None + 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/", defaults={"with_query": False}) +@bp.route("/assiduites//query", defaults={"with_query": True}) +@api_web_bp.route("/assiduites/", defaults={"with_query": False}) +@api_web_bp.route("/assiduites//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/ + + Un filtrage peut être donné avec une query + chemin : /assiduites//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: + for ass in assiduites.all(): data = ass.to_dict() data_set.append(change_etat(data)) return jsonify(data_set) -@bp.route("/assiduite//create", methods=["POST"]) +@bp.route("/assiduites/formsemestre/") +@api_web_bp.route("/assiduites/formsemestre/") +@login_required @scodoc @permission_required(Permission.ScoView) -def create(etuid: int = None): +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: + data = ass.to_dict() + data_set.append(change_etat(data)) + + return jsonify(data_set) + + else: + return json_error(404, "le paramètre 'formsemestre_id doit être renseigné'") + + +@bp.route("/assiduite//create", methods=["POST"], defaults={"batch": False}) +@api_web_bp.route( + "/assiduite//create", methods=["POST"], defaults={"batch": False} +) +@bp.route( + "/assiduite//create/batch", methods=["POST"], defaults={"batch": True} +) +@api_web_bp.route( + "/assiduite//create/batch", methods=["POST"], defaults={"batch": True} +) +@scodoc +@permission_required(Permission.ScoView) +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//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//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//edit", methods=["POST"], defaults={"batch": False} +) +@api_web_bp.route( + "/assiduite//edit", methods=["POST"], defaults={"batch": False} +) +@bp.route( + "/assiduite//edit/batch", + methods=["POST"], + defaults={"batch": True}, +) +@api_web_bp.route( + "/assiduite//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 diff --git a/app/models/assiduites.py b/app/models/assiduites.py index 986c4aca..aeb3b200 100644 --- a/app/models/assiduites.py +++ b/app/models/assiduites.py @@ -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 diff --git a/app/scodoc/sco_assiduites.py b/app/scodoc/sco_assiduites.py new file mode 100644 index 00000000..a3d8c406 --- /dev/null +++ b/app/scodoc/sco_assiduites.py @@ -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) diff --git a/app/scodoc/sco_utils.py b/app/scodoc/sco_utils.py index 6ad70430..46255a50 100644 --- a/app/scodoc/sco_utils.py +++ b/app/scodoc/sco_utils.py @@ -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."""