############################################################################## # ScoDoc # Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved. # See LICENSE ############################################################################## """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.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 import app.scodoc.sco_utils as scu @bp.route("/assiduite/") @scodoc @permission_required(Permission.ScoView) def assiduite(assiduiteid: int = None): """Retourne un objet assiduité à partir de son id Exemple de résultat: { "assiduiteid": 1, "etuid": 2, "moduleimpl_id": 3, "date_debut": "2022-10-31T08:00", "date_fin": "2022-10-31T10:00", "etat": "retard" } """ assiduite = Assiduite.query.get(assiduiteid) if assiduite is None: return json_error(404, message="assiduité inexistante") data = assiduite.to_dict() return jsonify(change_etat(data)) @bp.route("/assiduites/", defaults={"with_query": False}) @bp.route("/assiduites//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) 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() 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] # cas 2 : date de début deb = request.args.get("date_debut") deb: datetime = 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) # cas 3 : date de fin fin = request.args.get("date_fin") fin = 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) # cas 4 : moduleimpl_id module = request.args.get("moduleimpl_id") try: module = int(module) except Exception: module = None if module is not None: assiduites = [ass for ass in assiduites if ass.moduleimpl_id == module] data_set: List[dict] = [] for ass in assiduites: data = ass.to_dict() data_set.append(change_etat(data)) return jsonify(data_set) @bp.route("/assiduite//create", methods=["POST"]) @scodoc @permission_required(Permission.ScoView) def create(etuid: int = None): """ Création d'une assiduité pour l'étudiant (etuid) La requête doit avoir un content type "application/json": { "date_debut": str, "date_fin": str, "etat": str, } ou { "date_debut": str, "date_fin": str, "etat": str, "moduleimpl_id": int, } """ etud: Identite = Identite.query.filter_by(id=etuid).first_or_404() data = request.get_json(force=True) errors: List[str] = [] # -- vérifications de l'objet json -- # cas 1 : ETAT etat = data.get("etat", None) if etat is None: errors.append("param 'etat': manquant") elif etat not in scu.ETATS_ASSIDUITE.keys(): errors.append("param 'etat': invalide") data = change_etat(data, False) etat = data.get("etat", None) # cas 2 : date_debut 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) if deb is None: errors.append("param 'date_debut': format invalide") # cas 3 : date_fin 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) if fin is None: errors.append(f"param 'date_fin': format invalide") # cas 4 : moduleimpl_id moduleimpl_id = data.get("moduleimpl_id", None) if moduleimpl_id is not None: try: moduleimpl_id: int = int(moduleimpl_id) if moduleimpl_id < 0: raise Exception except: errors.append("param 'moduleimpl_id': invalide") if errors != []: err: str = ", ".join(errors) return json_error(404, err) # TOUT EST OK nouv_assiduite: Assiduite or str = Assiduite.create_assiduite( date_debut=deb, date_fin=fin, etat=etat, etud=etud, module=moduleimpl_id, ) if type(nouv_assiduite) is Assiduite: return jsonify({"assiduiteid": nouv_assiduite.assiduiteid}) return json_error( 404, { 1: "La période sélectionnée est déjà couverte par une autre assiduite", 2: "L'étudiant ne participe pas au moduleimpl sélectionné", }.get(nouv_assiduite), ) @bp.route("/assiduite//delete", methods=["POST"]) @login_required @scodoc @permission_required(Permission.ScoAssiduiteChange) def delete(assiduiteid: int): """ Suppression d'une assiduité à partir de son id """ assiduite: Assiduite = Assiduite.query.filter_by(id=assiduiteid).first_or_404() db.session.delete(assiduite) db.session.commit() return jsonify({"OK": True}) @bp.route("/assiduite//edit", methods=["POST"]) @login_required @scodoc @permission_required(Permission.ScoAssiduiteChange) def edit(assiduiteid: int): """ Edition d'une assiduité à partir de son id La requête doit avoir un content type "application/json": { "etat": str, "moduleimpl_id": int } """ assiduite: Assiduite = Assiduite.query.filter_by(id=assiduiteid).first_or_404() errors: List[str] = [] data = request.get_json(force=True) # Vérifications de data # Cas 1 : Etat if data.get("etat") is not None: data = change_etat(data, False) if data.get("etat") is None: errors.append("param 'etat': invalide") else: assiduite.etat = data.get("etat") # Cas 2 : Moduleimpl_id moduleimpl_id = data.get("moduleimpl_id", False) if moduleimpl_id is not False: try: if moduleimpl_id is not None: moduleimpl_id: int = int(moduleimpl_id) if moduleimpl_id < 0 or not Assiduite.verif_moduleimpl( moduleimpl_id, assiduite.etudid ): raise Exception assiduite.moduleimpl_id = moduleimpl_id except: errors.append("param 'moduleimpl_id': invalide") if errors != []: err: str = ", ".join(errors) return json_error(404, err) db.session.add(assiduite) db.session.commit() return jsonify({"OK": True}) # -- 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 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