diff --git a/Rapport.md b/Rapport.md new file mode 100644 index 0000000000000000000000000000000000000000..8e9defd97534f8ce3b813f844d76e9ee2a4bb97f --- /dev/null +++ b/Rapport.md @@ -0,0 +1,139 @@ +# Compte rendu analyse du ticket 1023 + +## 1. Le ticket + +Ticket décrit dans la page https://git.scodoc.org/Scodoc/Scodoc/issues/1023 + + + +Proposition de collègue: + +L'envoi de mail en cas d absences trop nombreuses est bien utilisé mais présentes quelques petits défauts: +le mail part à après la saisie de l absence mais la justification peut arriver (selon notre réglement des études) dans les trois jours qui suivent. Cela provoque donc quelques faux positifs +Certains directeurs d'étude traitent les absence selon un rithme régulier (semaine/mois). il leur faut donc reprendre l'histoques des mails qui peuvent être nombreux (surtout en première année) + +proposition: +remplacer l'envoi immédiat par un envoi régulier (hebdomadaire, mensuel) d'une compilation des alertes du mois. les absences injustifiées des étudiants qui ont eu plus de x absences injustifiées depuis le début du semestre. + + +## 2. Analyse du ticket + +Classification : On peut considérer le ticket comme une maintenance **corrective** ou +**évolutive**. + +Nous classiferions comme évolutive, car la fonctionnalité fonctionnait mais ne correspondait pas totalement au besoin. + +## 2.1 Reproduire le problème + +Etudiant : Valentin FEBLAR + +Classe email avec méthode _send-message_ -> /app/email.py + +Utilisation de la classe email pour les absences -> app/scodoc/sco_abs_notification.py + +Classe assiduites.py -> app/models/assiduites.py + appelle la méthode _create-assiduite_ + qui lui va appeler sco_abs_notifaction.abs_notify + qui lui va appeler do_abs_notify + qui lui va appeler abs_notify_send + qui lui va appeler email.send_message + +test avec les log et tout -> problème à partir de l'appel du abs_notify_send dans le abs_notify => invalid_formsemestre + +for destinations -> vide car à la création le abs_notify_get_destinations possède un formsemestre invalide + +Problème trouvé -> voir screen fait + +## 2.2 Analyse de la solution + +- Dans la classe app/api/billets_absences.py se trouve une méthode billets_absence_etudiant qui renvoie la liste des absences d'un étudiant donné + +- Gestion des absences dans la bases de données se déroule dans la classe app/models/absences.py -> dedans il crée une classe BilletAbsence + +- classe app/api/assiduites.py contient une méthode assiduites qui retourne la liste des absences d'un étudiant + +- classe app/models/assiduites.py contient une méthode get_assiduite qui retourne la liste des assiduités pour un dept + +- classe CountCalculator dans le fichier app/scodoc/sco_assiduites.py TRES INTERESSANTE + +- Voir photo avec tableau par groupe et période de toutes les absences d'un groupe, création du fichier dans la classe app/views/assiduites.py -> méthode visu_asssi_group() + +- ON AIMES LA CLASSE TableAsssi dans le fichier app/tables/visu_assiduites.py (utilise la clase CountCalculator) + +- méthode est_courant dans app/models/formesemestre.py qui permet de savoir si la période d'un semestre est compris dans la date actuelle + +- méthode groups_table dans app/scodoc/sco_groups_view.py qui retourne la liste des étudiants dans un semestre + +- méthode get_default_group dans app/scodoc/sco_groups.py qui retourne les groupes d'un formsemester + +- méthode _get_etud_stats dans app/tables/visu_assiduites.py qui retoune le nombre absences et nbr absences justifiées + +- méthode get_assiduites_stats dans app/scodoc/sco_assiduites.py qui permet de récuperer les assiduites d'un etudiant pour une periode donnée + +- méthode _list_formsemestres_query dans app/api/formesemestre.py qui permet de récupérer les formesemestre + +Première étape : récuperer la liste de tout les semestres + - méthode +Deuxième étape : faire une boucle sur la liste des semestres et leur appliqué la méthode est_courant (ne garder que ceux qui retourne true) + - méthode est_courant dans app/models/formesemestre.py qui permet de savoir si la période d'un semestre est compris dans la date actuelle +Troisième étape : récupérer la liste d'étudiants (à partir du formsemestre) + - méthode groups_table dans app/scodoc/sco_groups_view.py qui retourne la liste des étudiants dans un semestre +Quatrième étape : Récuperer les absences par étudiant + - méthode get_default_group dans app/scodoc/sco_groups.py qui retourne les groupes d'un formsemester + - création d'un objet TableAsssi dans le fichier app/tables/visu_assiduites.py ( qui utilise la clase CountCalculator) + - méthode _get_etud_stats dans app/tables/visu_assiduites.py qui retoune le nombre absences et nbr absences justifiées +Cinquième étape : Pour les étudiants pré sélectionné, on récuoère leurs absences du mois dernier + - méthode get_assiduites_stats dans app/scodoc/sco_assiduites.py qui permet de récuperer les assiduites d'un etudiant pour une periode donnée + + +## 2.3 Proposition de la solution + +Création d'un scheduler/workflow qui va lancer chaque mois, une méthode qui va générer et envoyer un mail à chaque étudiants qui ont plus de x absences injustifiées dans le semestre, résumant leurs absences injustifiées du mois précedent. + +Création du scheduler : + +- Utilisation du module python apsscheduler : + + from apsscheduler.schedulers.background import BackGroundScheduler + +- Création d'une méthode pour instancier notre scheduler et le lancer : + + ``` + def start_scheduler(): + scheduler = BackgroundScheduler(timezone='Europe/Paris') + scheduler.add_job(job, 'interval', id='my_task1', seconds=10) + scheduler.start() + return scheduler + ``` + Ce scheduler s'execute toutes les 10 secondes + +- On va appeler le scheduler pour lancer l'execution en arrière plan, avec le lancement de l'app en premier lan + + ``` + def create_app(config_class=DevConfig): + #Contenu initial de la méthode + #.... + start_scheduler() + return app + ``` + + +- Implémenter le job que le scheduler va lancer (envoie mail) + + code + +- Paramétrer le scheduler (fréquence execution) + + code + + +Suppression ancien mail : + +- Trouver sa place dans le code + + code + +- Retirer l'envoie immédiat + + + diff --git a/app/__init__.py b/app/__init__.py index b3944de7ce62475e16e5e5647b1a9b46e6b8efbc..82b064aed1c8b29adb93009235532fd02f0dd243 100755 --- a/app/__init__.py +++ b/app/__init__.py @@ -269,6 +269,19 @@ class ReverseProxied: environ["wsgi.url_scheme"] = scheme # ou forcer à https ici ? return self.app(environ, start_response) +import time +import datetime +def job(): + #if datetime.datetime.today().weekday() == 0: + print("I'm working...") + +from apscheduler.schedulers.background import BackgroundScheduler + +def start_scheduler(): + scheduler = BackgroundScheduler(timezone='Europe/Paris') + scheduler.add_job(job, 'interval', id='my_task1', seconds=10) + scheduler.start() + return scheduler def create_app(config_class=DevConfig): app = Flask(__name__, static_url_path="/ScoDoc/static", static_folder="static") @@ -468,9 +481,11 @@ def create_app(config_class=DevConfig): # Si la base n'a pas été upgradée (arrive durrant l'install) # il se peut que la table scodoc_site_config n'existe pas encore. pass + scheduler = start_scheduler() return app + def set_sco_dept(scodoc_dept: str, open_cnx=True): """Set global g object to given dept and open db connection if needed""" # Check that dept exists diff --git a/app/models/assiduites.py b/app/models/assiduites.py index c920bbe4490a17a8329165b008f5da00550d533f..6efbd02e8a05405f69d1e53506b694f9b871a916 100644 --- a/app/models/assiduites.py +++ b/app/models/assiduites.py @@ -122,7 +122,7 @@ class Assiduite(ScoDocModel): "external_data": self.external_data, } return data - + def __str__(self) -> str: "chaine pour journaux et debug (lisible par humain français, en timezone serveur)" try: @@ -142,7 +142,8 @@ class Assiduite(ScoDocModel): def __repr__(self) -> str: return f"<Assiduite {self.id}: {self.etudiant.nom}: {self.__str__()}>" - + + # TODO --> Retirer l'envoi de mail @classmethod def create_assiduite( cls, @@ -264,10 +265,11 @@ class Assiduite(ScoDocModel): etudid=etud.id, msg=f"assiduité: {nouv_assiduite}", ) - if notify_mail and etat == EtatAssiduite.ABSENT: - sco_abs_notification.abs_notify(etud.id, nouv_assiduite.date_debut) + # Suppresion du notify + # if notify_mail and etat == EtatAssiduite.ABSENT: + # sco_abs_notification.abs_notify(etud.id, nouv_assiduite.date_debut) return nouv_assiduite - + # TODO --> Ajout d'un scheduler qui envoie un mail à tout les étudiants def set_moduleimpl(self, moduleimpl_id: int | str): """Mise à jour du moduleimpl_id Les valeurs du champ "moduleimpl_id" possibles sont : @@ -425,8 +427,8 @@ class Assiduite(ScoDocModel): if g.scodoc_dept: query = query.join(Identite).filter_by(dept_id=g.scodoc_dept_id) return query.first_or_404() - - + # TODO --> Créer une requete qui retourne une liste d'etudiant avec leurs abscences injustifiées pour le mois. + class Justificatif(ScoDocModel): """ Représente un justificatif: @@ -856,3 +858,4 @@ def has_assiduites_disable_pref(formsemestre: FormSemestre) -> str | bool: pref = pref.strip() return pref if pref else False + diff --git a/app/models/assiduitesSenderScheduler.py b/app/models/assiduitesSenderScheduler.py new file mode 100644 index 0000000000000000000000000000000000000000..5c96e343078d37a9a369d8b4cf024414e3a83b6a --- /dev/null +++ b/app/models/assiduitesSenderScheduler.py @@ -0,0 +1,15 @@ +from app import log +import schedule +import time +import datetime +def job(): + #if datetime.datetime.today().weekday() == 0: + print("I'm working...") + log( + "Le scheduler est là ou pas ??" + ) +from apscheduler.schedulers.background import BlockingScheduler +def starter(): + scheduler = BlockingScheduler(timezone='Europe/Paris') + scheduler.add_job(job, 'interval', id='my_task1', seconds=10) + scheduler.start() diff --git a/app/scodoc/sco_abs_billets.py b/app/scodoc/sco_abs_billets.py index ecb3b33c74b7ba1e6397868d039fad1729c9dc5d..a5bb54cffea4f6f9b9e5d8d11b00cf862cbf4346 100644 --- a/app/scodoc/sco_abs_billets.py +++ b/app/scodoc/sco_abs_billets.py @@ -48,6 +48,7 @@ def query_billets_etud(etudid: int = None, etat: bool = None) -> Query: ): return [] billets = BilletAbsence.query + billets = billets.filter_by(date ) if etudid is not None: billets = billets.filter_by(etudid=etudid) if etat is not None: diff --git a/images/AbsenceVal.png b/images/AbsenceVal.png new file mode 100644 index 0000000000000000000000000000000000000000..bd5706d53679adebd93fad1406160eadf0daa697 Binary files /dev/null and b/images/AbsenceVal.png differ diff --git a/images/AbsenceVal2.png b/images/AbsenceVal2.png new file mode 100644 index 0000000000000000000000000000000000000000..3fee4aea1ea30a26d97fdd629f83e0c12a571335 Binary files /dev/null and b/images/AbsenceVal2.png differ diff --git a/images/AbsenceVal3.png b/images/AbsenceVal3.png new file mode 100644 index 0000000000000000000000000000000000000000..bd5706d53679adebd93fad1406160eadf0daa697 Binary files /dev/null and b/images/AbsenceVal3.png differ diff --git a/images/AbsenceVal4.png b/images/AbsenceVal4.png new file mode 100644 index 0000000000000000000000000000000000000000..3fee4aea1ea30a26d97fdd629f83e0c12a571335 Binary files /dev/null and b/images/AbsenceVal4.png differ diff --git a/images/LogMail.png b/images/LogMail.png new file mode 100644 index 0000000000000000000000000000000000000000..16d2e7991a980f2fe18db6a12c19da085aa55fe2 Binary files /dev/null and b/images/LogMail.png differ diff --git a/images/Methode pour recup tout les formsem.png b/images/Methode pour recup tout les formsem.png new file mode 100644 index 0000000000000000000000000000000000000000..d85c49bd1bc154da754ce3b1648a56b4bcc963e0 Binary files /dev/null and b/images/Methode pour recup tout les formsem.png differ diff --git a/images/ParamScodocMail.png b/images/ParamScodocMail.png new file mode 100644 index 0000000000000000000000000000000000000000..566e446ba061aeeafc23cfd1653128d7891bd3c2 Binary files /dev/null and b/images/ParamScodocMail.png differ diff --git a/images/RecapAbsencePourUnSemestre.png b/images/RecapAbsencePourUnSemestre.png new file mode 100644 index 0000000000000000000000000000000000000000..056e6b528a99abe0ed53f996b8a039e2fd27429a Binary files /dev/null and b/images/RecapAbsencePourUnSemestre.png differ diff --git a/images/RecepMail.png b/images/RecepMail.png new file mode 100644 index 0000000000000000000000000000000000000000..17bbad618e47167aea4a1525348c134d685dcc9d Binary files /dev/null and b/images/RecepMail.png differ