diff --git a/app/models/assiduites.py b/app/models/assiduites.py index 7e9ab274..908629bd 100644 --- a/app/models/assiduites.py +++ b/app/models/assiduites.py @@ -2,15 +2,24 @@ """Gestion de l'assiduité (assiduités + justificatifs) """ from datetime import datetime - +from flask_login import current_user from flask_sqlalchemy.query import Query from app import db, log, g, set_sco_dept -from app.models import ModuleImpl, Module, Scolog, FormSemestre, FormSemestreInscription +from app.models import ( + ModuleImpl, + Module, + Scolog, + FormSemestre, + FormSemestreInscription, + ScoDocModel, +) from app.models.etudiants import Identite from app.auth.models import User from app.scodoc import sco_abs_notification +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_utils import ( EtatAssiduite, EtatJustificatif, @@ -19,7 +28,7 @@ from app.scodoc.sco_utils import ( ) -class Assiduite(db.Model): +class Assiduite(ScoDocModel): """ Représente une assiduité: - une plage horaire lié à un état et un étudiant @@ -136,7 +145,7 @@ class Assiduite(db.Model): notify_mail=False, ) -> "Assiduite": """Créer une nouvelle assiduité pour l'étudiant. - Les datetime doivent être en timzone serveur. + Les datetime doivent être en timezone serveur. Raises ScoValueError en cas de conflit ou erreur. """ if date_debut.tzinfo is None: @@ -301,7 +310,7 @@ class Assiduite(db.Model): return "Non spécifié" if traduire else None -class Justificatif(db.Model): +class Justificatif(ScoDocModel): """ Représente un justificatif: - une plage horaire lié à un état et un étudiant @@ -356,6 +365,14 @@ class Justificatif(db.Model): external_data = db.Column(db.JSON, nullable=True) + @classmethod + def get_justificatif(cls, justif_id: int) -> "Justificatif": + """Justificatif ou 404, cherche uniquement dans le département courant""" + query = Justificatif.query.filter_by(id=justif_id) + if g.scodoc_dept: + query = query.join(Identite).filter_by(dept_id=g.scodoc_dept_id) + return query.first_or_404() + def to_dict(self, format_api: bool = False) -> dict: """transformation de l'objet en dictionnaire sérialisable""" @@ -398,10 +415,25 @@ class Justificatif(db.Model): self.date_fin.strftime("%d/%m/%Y %Hh%M") }""" + @classmethod + def convert_dict_fields(cls, args: dict) -> dict: + """Convert fields. Called by ScoDocModel's create_from_dict, edit and from_dict + Raises ScoValueError si paramètres incorrects. + """ + if not isinstance(args["date_debut"], datetime) or not isinstance( + args["date_fin"], datetime + ): + raise ScoValueError("type date incorrect") + if args["date_fin"] <= args["date_debut"]: + raise ScoValueError("dates incompatibles") + if args["entry_date"] and not isinstance(args["entry_date"], datetime): + raise ScoValueError("type entry_date incorrect") + return args + @classmethod def create_justificatif( cls, - etud: Identite, + etudiant: Identite, date_debut: datetime, date_fin: datetime, etat: EtatJustificatif, @@ -410,24 +442,15 @@ class Justificatif(db.Model): user_id: int = None, external_data: dict = None, ) -> "Justificatif": - """Créer un nouveau justificatif pour l'étudiant""" - nouv_justificatif = Justificatif( - date_debut=date_debut, - date_fin=date_fin, - etat=etat, - etudiant=etud, - raison=raison, - entry_date=entry_date, - user_id=user_id, - external_data=external_data, - ) - - db.session.add(nouv_justificatif) + """Créer un nouveau justificatif pour l'étudiant. + Raises ScoValueError si paramètres incorrects. + """ + nouv_justificatif = cls.create_from_dict(locals()) db.session.commit() - log(f"create_justificatif: etudid={etud.id} {nouv_justificatif}") + log(f"create_justificatif: etudid={etudiant.id} {nouv_justificatif}") Scolog.logdb( method="create_justificatif", - etudid=etud.id, + etudid=etudiant.id, msg=f"justificatif: {nouv_justificatif}", ) return nouv_justificatif @@ -468,6 +491,25 @@ class Justificatif(db.Model): True, ) + def get_fichiers(self) -> tuple[list[str], int]: + """Renvoie la liste des noms de fichiers justicatifs + accessibles par l'utilisateur courant et le nombre total + de fichiers. + (ces fichiers sont dans l'archive associée) + """ + if self.fichier is None: + return [], 0 + archive_name: str = self.fichier + archiver: JustificatifArchiver = JustificatifArchiver() + filenames = archiver.list_justificatifs(archive_name, self.etudiant) + accessible_filenames = [] + for filename in filenames: + if int(filename[1]) == current_user.id or current_user.has_permission( + Permission.AbsJustifView + ): + accessible_filenames.append(filename[0]) + return accessible_filenames, len(filenames) + def is_period_conflicting( date_debut: datetime,