2020-09-26 16:19:37 +02:00
# -*- mode: python -*-
# -*- coding: utf-8 -*-
##############################################################################
#
# Gestion scolarite IUT
#
2023-01-02 13:16:27 +01:00
# Copyright (c) 1999 - 2023 Emmanuel Viennet. All rights reserved.
2020-09-26 16:19:37 +02:00
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
# Emmanuel Viennet emmanuel.viennet@viennet.net
#
##############################################################################
""" Système de notification par mail des excès d ' absences
( see ticket #147)
Il suffit d ' appeler abs_notify() après chaque ajout d ' absence .
"""
2021-02-04 20:02:44 +01:00
import datetime
2020-09-26 16:19:37 +02:00
2021-07-11 17:37:12 +02:00
from flask import g , url_for
2021-08-26 23:43:54 +02:00
from flask_mail import Message
2022-03-05 12:47:08 +01:00
from app . models . formsemestre import FormSemestre
2021-07-11 17:37:12 +02:00
2021-06-19 23:21:37 +02:00
import app . scodoc . notesdb as ndb
import app . scodoc . sco_utils as scu
2021-08-29 19:57:32 +02:00
from app import log
2021-06-19 23:21:37 +02:00
from app . scodoc . scolog import logdb
2021-07-03 23:35:32 +02:00
from app . scodoc import sco_etud
2021-06-19 23:21:37 +02:00
from app . scodoc import sco_formsemestre
from app . scodoc import sco_preferences
2021-07-03 23:35:32 +02:00
from app . scodoc import sco_users
2021-08-26 23:43:54 +02:00
from app import email
2020-09-26 16:19:37 +02:00
2021-08-21 00:24:51 +02:00
def abs_notify ( etudid , date ) :
2020-09-26 16:19:37 +02:00
""" Check if notifications are requested and send them
2020-12-15 08:35:44 +01:00
Considère le nombre d ' absence dans le semestre courant
2020-09-26 16:19:37 +02:00
( s ' il n ' y a pas de semestre courant , ne fait rien ,
car l ' etudiant n ' est pas inscrit au moment de l ' absence!).
"""
2021-06-19 23:21:37 +02:00
from app . scodoc import sco_abs
2022-03-05 12:47:08 +01:00
formsemestre = retreive_current_formsemestre ( etudid , date )
if not formsemestre :
2020-09-26 16:19:37 +02:00
return # non inscrit a la date, pas de notification
2022-03-05 12:47:08 +01:00
nbabs , nbabsjust = sco_abs . get_abs_count_in_interval (
etudid , formsemestre . date_debut . isoformat ( ) , formsemestre . date_fin . isoformat ( )
)
do_abs_notify ( formsemestre , etudid , date , nbabs , nbabsjust )
2020-09-26 16:19:37 +02:00
2022-03-05 12:47:08 +01:00
def do_abs_notify ( formsemestre : FormSemestre , etudid , date , nbabs , nbabsjust ) :
2020-12-15 08:35:44 +01:00
""" Given new counts of absences, check if notifications are requested and send them. """
2020-09-26 16:19:37 +02:00
# prefs fallback to global pref if sem is None:
2022-03-05 12:47:08 +01:00
if formsemestre :
formsemestre_id = formsemestre . id
2020-09-26 16:19:37 +02:00
else :
formsemestre_id = None
2022-03-05 12:47:08 +01:00
prefs = sco_preferences . SemPreferences ( formsemestre_id = formsemestre_id )
2020-09-26 16:19:37 +02:00
destinations = abs_notify_get_destinations (
2022-03-05 12:47:08 +01:00
formsemestre , prefs , etudid , date , nbabs , nbabsjust
2020-09-26 16:19:37 +02:00
)
2022-03-05 12:47:08 +01:00
msg = abs_notification_message ( formsemestre , prefs , etudid , nbabs , nbabsjust )
2020-09-26 16:19:37 +02:00
if not msg :
return # abort
# Vérification fréquence (pour ne pas envoyer de mails trop souvent)
2021-07-28 17:03:54 +02:00
abs_notify_max_freq = sco_preferences . get_preference ( " abs_notify_max_freq " )
2020-09-26 16:19:37 +02:00
destinations_filtered = [ ]
for email_addr in destinations :
2021-08-21 00:24:51 +02:00
nbdays_since_last_notif = user_nbdays_since_last_notif ( email_addr , etudid )
2020-09-26 16:19:37 +02:00
if ( nbdays_since_last_notif is None ) or (
nbdays_since_last_notif > = abs_notify_max_freq
) :
destinations_filtered . append ( email_addr )
if destinations_filtered :
abs_notify_send (
destinations_filtered ,
etudid ,
msg ,
nbabs ,
nbabsjust ,
formsemestre_id ,
)
2021-08-21 00:24:51 +02:00
def abs_notify_send ( destinations , etudid , msg , nbabs , nbabsjust , formsemestre_id ) :
2020-09-26 16:19:37 +02:00
""" Actually send the notification by email, and register it in database """
2021-06-15 13:59:56 +02:00
cnx = ndb . GetDBConnexion ( )
2020-09-26 16:19:37 +02:00
log ( " abs_notify: sending notification to %s " % destinations )
2021-02-03 22:00:41 +01:00
cursor = cnx . cursor ( cursor_factory = ndb . ScoDocCursor )
2021-08-26 23:43:54 +02:00
for dest_addr in destinations :
msg . recipients = [ dest_addr ]
email . send_message ( msg )
2021-02-03 22:00:41 +01:00
ndb . SimpleQuery (
2021-08-26 23:43:54 +02:00
""" INSERT into absences_notifications
2021-08-13 00:34:58 +02:00
( etudid , email , nbabs , nbabsjust , formsemestre_id )
VALUES ( % ( etudid ) s , % ( email ) s , % ( nbabs ) s , % ( nbabsjust ) s , % ( formsemestre_id ) s )
""" ,
2021-08-29 08:17:12 +02:00
{
" etudid " : etudid ,
" email " : dest_addr ,
" nbabs " : nbabs ,
" nbabsjust " : nbabsjust ,
" formsemestre_id " : formsemestre_id ,
} ,
2020-09-26 16:19:37 +02:00
cursor = cursor ,
)
logdb (
cnx = cnx ,
method = " abs_notify " ,
etudid = etudid ,
msg = " sent to %s (nbabs= %d ) " % ( destinations , nbabs ) ,
)
2022-03-05 12:47:08 +01:00
def abs_notify_get_destinations (
formsemestre : FormSemestre , prefs , etudid , date , nbabs , nbabsjust
) - > set :
2020-12-15 08:35:44 +01:00
""" Returns set of destination emails to be notified """
2020-09-26 16:19:37 +02:00
destinations = [ ] # list of email address to notify
2022-03-05 12:47:08 +01:00
if abs_notify_is_above_threshold ( etudid , nbabs , nbabsjust , formsemestre . id ) :
if prefs [ " abs_notify_respsem " ] :
2020-09-26 16:19:37 +02:00
# notifie chaque responsable du semestre
2022-03-05 12:47:08 +01:00
for responsable in formsemestre . responsables :
if responsable . email :
destinations . append ( responsable . email )
2020-09-26 16:19:37 +02:00
if prefs [ " abs_notify_chief " ] and prefs [ " email_chefdpt " ] :
destinations . append ( prefs [ " email_chefdpt " ] )
if prefs [ " abs_notify_email " ] :
destinations . append ( prefs [ " abs_notify_email " ] )
if prefs [ " abs_notify_etud " ] :
2021-08-22 13:24:36 +02:00
etud = sco_etud . get_etud_info ( etudid = etudid , filled = True ) [ 0 ]
2020-09-26 16:19:37 +02:00
if etud [ " email_default " ] :
destinations . append ( etud [ " email_default " ] )
# Notification (à chaque fois) des resp. de modules ayant des évaluations
# à cette date
# nb: on pourrait prevoir d'utiliser un autre format de message pour ce cas
2022-03-05 12:47:08 +01:00
if prefs [ " abs_notify_respeval " ] :
2021-08-21 00:24:51 +02:00
mods = mod_with_evals_at_date ( date , etudid )
2020-09-26 16:19:37 +02:00
for mod in mods :
2021-07-03 23:35:32 +02:00
u = sco_users . user_info ( mod [ " responsable_id " ] )
2020-09-26 16:19:37 +02:00
if u [ " email " ] :
destinations . append ( u [ " email " ] )
# uniq
destinations = set ( destinations )
return destinations
2021-08-21 00:24:51 +02:00
def abs_notify_is_above_threshold ( etudid , nbabs , nbabsjust , formsemestre_id ) :
2020-09-26 16:19:37 +02:00
""" True si il faut notifier les absences (indépendemment du destinataire)
nbabs : nombre d ' absence (de tous types, unité de compte = demi-journée)
nbabsjust : nombre d ' absences justifiées
2020-12-15 08:35:44 +01:00
( nbabs > abs_notify_abs_threshold )
2020-09-26 16:19:37 +02:00
( nbabs - nbabs_last_notified ) > abs_notify_abs_increment
"""
2021-06-14 00:23:22 +02:00
abs_notify_abs_threshold = sco_preferences . get_preference (
2021-07-28 17:03:54 +02:00
" abs_notify_abs_threshold " , formsemestre_id
2020-09-26 16:19:37 +02:00
)
2021-06-14 00:23:22 +02:00
abs_notify_abs_increment = sco_preferences . get_preference (
2021-07-28 17:03:54 +02:00
" abs_notify_abs_increment " , formsemestre_id
2020-09-26 16:19:37 +02:00
)
2021-08-21 00:24:51 +02:00
nbabs_last_notified = etud_nbabs_last_notified ( etudid , formsemestre_id )
2020-09-26 16:19:37 +02:00
if nbabs_last_notified == 0 :
if nbabs > abs_notify_abs_threshold :
return True # first notification
else :
return False
else :
if ( nbabs - nbabs_last_notified ) > = abs_notify_abs_increment :
return True
return False
2021-08-21 00:24:51 +02:00
def etud_nbabs_last_notified ( etudid , formsemestre_id = None ) :
2020-09-26 16:19:37 +02:00
""" nbabs lors de la dernière notification envoyée pour cet étudiant dans ce semestre
ou sans semestre ( ce dernier cas est nécessaire pour la transition au nouveau code ) """
2021-06-15 13:59:56 +02:00
cnx = ndb . GetDBConnexion ( )
2021-02-03 22:00:41 +01:00
cursor = cnx . cursor ( cursor_factory = ndb . ScoDocCursor )
2020-09-26 16:19:37 +02:00
cursor . execute (
""" select * from absences_notifications where etudid = %(etudid)s and (formsemestre_id = %(formsemestre_id)s or formsemestre_id is NULL) order by notification_date desc """ ,
vars ( ) ,
)
res = cursor . dictfetchone ( )
if res :
return res [ " nbabs " ]
else :
return 0
2021-08-21 00:24:51 +02:00
def user_nbdays_since_last_notif ( email_addr , etudid ) :
2020-09-26 16:19:37 +02:00
""" nb days since last notification to this email, or None if no previous notification """
2021-06-15 13:59:56 +02:00
cnx = ndb . GetDBConnexion ( )
2021-02-03 22:00:41 +01:00
cursor = cnx . cursor ( cursor_factory = ndb . ScoDocCursor )
2020-09-26 16:19:37 +02:00
cursor . execute (
2021-09-13 22:10:01 +02:00
""" SELECT * FROM absences_notifications
WHERE email = % ( email_addr ) s and etudid = % ( etudid ) s
ORDER BY notification_date DESC
""" ,
2020-09-26 16:19:37 +02:00
{ " email_addr " : email_addr , " etudid " : etudid } ,
)
res = cursor . dictfetchone ( )
if res :
2021-02-05 18:21:34 +01:00
now = datetime . datetime . now ( res [ " notification_date " ] . tzinfo )
return ( now - res [ " notification_date " ] ) . days
2020-09-26 16:19:37 +02:00
else :
return None
2022-03-05 12:47:08 +01:00
def abs_notification_message (
formsemestre : FormSemestre , prefs , etudid , nbabs , nbabsjust
) :
2020-09-26 16:19:37 +02:00
""" Mime notification message based on template.
2021-08-26 23:43:54 +02:00
returns a Message instance
or None if sending should be canceled ( empty template ) .
2020-09-26 16:19:37 +02:00
"""
2021-06-19 23:21:37 +02:00
from app . scodoc import sco_bulletins
etud = sco_etud . get_etud_info ( etudid = etudid , filled = True ) [ 0 ]
2020-09-26 16:19:37 +02:00
# Variables accessibles dans les balises du template: %(nom_variable)s :
2022-03-05 12:47:08 +01:00
values = sco_bulletins . make_context_dict ( formsemestre , etud )
2020-09-26 16:19:37 +02:00
values [ " nbabs " ] = nbabs
values [ " nbabsjust " ] = nbabsjust
values [ " nbabsnonjust " ] = nbabs - nbabsjust
2021-07-11 17:37:12 +02:00
values [ " url_ficheetud " ] = url_for (
2022-03-16 19:06:20 +01:00
" scolar.ficheEtud " , scodoc_dept = g . scodoc_dept , etudid = etudid , _external = True
2021-07-11 17:37:12 +02:00
)
2020-09-26 16:19:37 +02:00
template = prefs [ " abs_notification_mail_tmpl " ]
if template :
txt = prefs [ " abs_notification_mail_tmpl " ] % values
else :
log ( " abs_notification_message: empty template, not sending message " )
return None
2021-08-26 23:43:54 +02:00
subject = """ [ScoDoc] Trop d ' absences pour %(nomprenom)s """ % etud
msg = Message ( subject , sender = prefs [ " email_from_addr " ] )
msg . body = txt
2020-09-26 16:19:37 +02:00
return msg
2022-03-05 12:47:08 +01:00
def retreive_current_formsemestre ( etudid : int , cur_date ) - > FormSemestre :
2020-09-26 16:19:37 +02:00
""" Get formsemestre dans lequel etudid est (ou était) inscrit a la date indiquée
date est une chaine au format ISO ( yyyy - mm - dd )
2022-03-05 12:47:08 +01:00
Result : FormSemestre ou None si pas inscrit à la date indiquée
2020-09-26 16:19:37 +02:00
"""
2021-08-08 17:38:46 +02:00
req = """ SELECT i.formsemestre_id
FROM notes_formsemestre_inscription i , notes_formsemestre sem
WHERE sem . id = i . formsemestre_id AND i . etudid = % ( etudid ) s
AND ( % ( cur_date ) s > = sem . date_debut ) AND ( % ( cur_date ) s < = sem . date_fin )
"""
2020-09-26 16:19:37 +02:00
2021-07-28 17:03:54 +02:00
r = ndb . SimpleDictFetch ( req , { " etudid " : etudid , " cur_date " : cur_date } )
2020-09-26 16:19:37 +02:00
if not r :
return None
# s'il y a plusieurs semestres, prend le premier (rarissime et non significatif):
2022-03-05 12:47:08 +01:00
formsemestre = FormSemestre . query . get ( r [ 0 ] [ " formsemestre_id " ] )
return formsemestre
2020-09-26 16:19:37 +02:00
2021-08-21 00:24:51 +02:00
def mod_with_evals_at_date ( date_abs , etudid ) :
2021-08-08 17:38:46 +02:00
""" Liste des moduleimpls avec des evaluations à la date indiquée """
req = """ SELECT m.id AS moduleimpl_id, m.*
FROM notes_moduleimpl m , notes_evaluation e , notes_moduleimpl_inscription i
WHERE m . id = e . moduleimpl_id AND e . moduleimpl_id = i . moduleimpl_id
2020-09-26 16:19:37 +02:00
AND i . etudid = % ( etudid ) s AND e . jour = % ( date_abs ) s """
2021-07-28 17:03:54 +02:00
r = ndb . SimpleDictFetch ( req , { " etudid " : etudid , " date_abs " : date_abs } )
2020-09-26 16:19:37 +02:00
return r