forked from ScoDoc/ScoDoc
Envois de mail:
- réglage de l'adresse origine From au niveau global et systémtisation de son utilisation. - ajout de logs, réglage du log par défaut. - modernisation de code.
This commit is contained in:
parent
50efadf421
commit
7fc3108886
@ -247,6 +247,7 @@ def create_app(config_class=DevConfig):
|
||||
migrate.init_app(app, db)
|
||||
login.init_app(app)
|
||||
mail.init_app(app)
|
||||
app.extensions["mail"].debug = 0 # disable copy of mails to stderr
|
||||
bootstrap.init_app(app)
|
||||
moment.init_app(app)
|
||||
cache.init_app(app)
|
||||
@ -545,10 +546,9 @@ def log_call_stack():
|
||||
|
||||
# Alarms by email:
|
||||
def send_scodoc_alarm(subject, txt):
|
||||
from app.scodoc import sco_preferences
|
||||
from app import email
|
||||
|
||||
sender = sco_preferences.get_preference("email_from_addr")
|
||||
sender = email.get_from_addr()
|
||||
email.send_email(subject, sender, ["exception@scodoc.org"], txt)
|
||||
|
||||
|
||||
|
@ -1,14 +1,14 @@
|
||||
# -*- coding: UTF-8 -*
|
||||
from flask import render_template, current_app
|
||||
from flask_babel import _
|
||||
from app.email import send_email
|
||||
from app.email import get_from_addr, send_email
|
||||
|
||||
|
||||
def send_password_reset_email(user):
|
||||
token = user.get_reset_password_token()
|
||||
send_email(
|
||||
"[ScoDoc] Réinitialisation de votre mot de passe",
|
||||
sender=current_app.config["SCODOC_MAIL_FROM"],
|
||||
sender=get_from_addr(),
|
||||
recipients=[user.email],
|
||||
text_body=render_template("email/reset_password.txt", user=user, token=token),
|
||||
html_body=render_template("email/reset_password.j2", user=user, token=token),
|
||||
|
32
app/email.py
32
app/email.py
@ -11,6 +11,8 @@ from flask import current_app, g
|
||||
from flask_mail import Message
|
||||
|
||||
from app import mail
|
||||
from app.models.departements import Departement
|
||||
from app.models.config import ScoDocSiteConfig
|
||||
from app.scodoc import sco_preferences
|
||||
|
||||
|
||||
@ -56,6 +58,7 @@ def send_message(msg: Message):
|
||||
In mail debug mode, addresses are discarded and all mails are sent to the
|
||||
specified debugging address.
|
||||
"""
|
||||
email_test_mode_address = False
|
||||
if hasattr(g, "scodoc_dept"):
|
||||
# on est dans un département, on peut accéder aux préférences
|
||||
email_test_mode_address = sco_preferences.get_preference(
|
||||
@ -81,6 +84,35 @@ Adresses d'origine:
|
||||
+ msg.body
|
||||
)
|
||||
|
||||
current_app.logger.info(
|
||||
f"""email sent to{' (mode test)' if email_test_mode_address else ''}: {msg.recipients}
|
||||
from sender {msg.sender}
|
||||
"""
|
||||
)
|
||||
Thread(
|
||||
target=send_async_email, args=(current_app._get_current_object(), msg)
|
||||
).start()
|
||||
|
||||
|
||||
def get_from_addr(dept_acronym: str = None):
|
||||
"""L'adresse "from" à utiliser pour envoyer un mail
|
||||
|
||||
Si le departement est spécifié, ou si l'attribut `g.scodoc_dept`existe,
|
||||
prend le `email_from_addr` des préférences de ce département si ce champ est non vide.
|
||||
Sinon, utilise le paramètre global `email_from_addr`.
|
||||
Sinon, la variable de config `SCODOC_MAIL_FROM`.
|
||||
"""
|
||||
dept_acronym = dept_acronym or getattr(g, "scodoc_dept", None)
|
||||
if dept_acronym:
|
||||
dept = Departement.query.filter_by(acronym=dept_acronym).first()
|
||||
if dept:
|
||||
from_addr = (
|
||||
sco_preferences.get_preference("email_from_addr", dept_id=dept.id) or ""
|
||||
).strip()
|
||||
if from_addr:
|
||||
return from_addr
|
||||
return (
|
||||
ScoDocSiteConfig.get("email_from_addr")
|
||||
or current_app.config["SCODOC_MAIL_FROM"]
|
||||
or "none"
|
||||
)
|
||||
|
@ -216,7 +216,7 @@ def send_email_notifications_entreprise(subject: str, entreprise: Entreprise):
|
||||
txt = "\n".join(txt)
|
||||
email.send_email(
|
||||
subject,
|
||||
sco_preferences.get_preference("email_from_addr"),
|
||||
email.get_from_addr(),
|
||||
[EntreprisePreferences.get_email_notifications],
|
||||
txt,
|
||||
)
|
||||
|
@ -31,8 +31,8 @@ Formulaires configuration Exports Apogée (codes)
|
||||
|
||||
from flask import flash, url_for, redirect, request, render_template
|
||||
from flask_wtf import FlaskForm
|
||||
from wtforms import BooleanField, SelectField, SubmitField
|
||||
|
||||
from wtforms import BooleanField, SelectField, StringField, SubmitField
|
||||
from wtforms.validators import Email, Optional
|
||||
import app
|
||||
from app.models import ScoDocSiteConfig
|
||||
import app.scodoc.sco_utils as scu
|
||||
@ -70,6 +70,12 @@ class ScoDocConfigurationForm(FlaskForm):
|
||||
(i, name.capitalize()) for (i, name) in enumerate(scu.MONTH_NAMES, start=1)
|
||||
],
|
||||
)
|
||||
email_from_addr = StringField(
|
||||
label="Adresse source des mails",
|
||||
description="""adresse email source (from) des mails émis par ScoDoc.
|
||||
Attention: si ce champ peut aussi être défini dans chaque département.""",
|
||||
validators=[Optional(), Email()],
|
||||
)
|
||||
submit_scodoc = SubmitField("Valider")
|
||||
cancel_scodoc = SubmitField("Annuler", render_kw={"formnovalidate": True})
|
||||
|
||||
@ -87,6 +93,7 @@ def configuration():
|
||||
"enable_entreprises": ScoDocSiteConfig.is_entreprises_enabled(),
|
||||
"month_debut_annee_scolaire": ScoDocSiteConfig.get_month_debut_annee_scolaire(),
|
||||
"month_debut_periode2": ScoDocSiteConfig.get_month_debut_periode2(),
|
||||
"email_from_addr": ScoDocSiteConfig.get("email_from_addr"),
|
||||
}
|
||||
)
|
||||
if request.method == "POST" and (
|
||||
@ -130,6 +137,8 @@ def configuration():
|
||||
scu.MONTH_NAMES[ScoDocSiteConfig.get_month_debut_periode2()-1]
|
||||
}"""
|
||||
)
|
||||
if ScoDocSiteConfig.set("email_from_addr", form_scodoc.data["email_from_addr"]):
|
||||
flash("Adresse email origine enregistrée")
|
||||
return redirect(url_for("scodoc.index"))
|
||||
|
||||
return render_template(
|
||||
|
@ -233,8 +233,7 @@ class ScolarNews(db.Model):
|
||||
txt = re.sub(r'<a.*?href\s*=\s*"(.*?)".*?>(.*?)</a>', r"\2: \1", txt)
|
||||
|
||||
subject = "[ScoDoc] " + self.NEWS_MAP.get(self.type, "?")
|
||||
sender = prefs["email_from_addr"]
|
||||
|
||||
sender = email.get_from_addr()
|
||||
email.send_email(subject, sender, destinations, txt)
|
||||
|
||||
@classmethod
|
||||
|
@ -32,20 +32,21 @@
|
||||
Il suffit d'appeler abs_notify() après chaque ajout d'absence.
|
||||
"""
|
||||
import datetime
|
||||
from typing import Optional
|
||||
|
||||
from flask import g, url_for
|
||||
from flask_mail import Message
|
||||
from app.models.formsemestre import FormSemestre
|
||||
|
||||
import app.scodoc.notesdb as ndb
|
||||
import app.scodoc.sco_utils as scu
|
||||
from app import db
|
||||
from app import email
|
||||
from app import log
|
||||
from app.scodoc.scolog import logdb
|
||||
from app.models.absences import AbsenceNotification
|
||||
from app.models.events import Scolog
|
||||
from app.models.formsemestre import FormSemestre
|
||||
import app.scodoc.notesdb as ndb
|
||||
from app.scodoc import sco_etud
|
||||
from app.scodoc import sco_formsemestre
|
||||
from app.scodoc import sco_preferences
|
||||
from app.scodoc import sco_users
|
||||
from app import email
|
||||
|
||||
|
||||
def abs_notify(etudid, date):
|
||||
@ -106,32 +107,24 @@ def do_abs_notify(formsemestre: FormSemestre, etudid, date, nbabs, nbabsjust):
|
||||
|
||||
def abs_notify_send(destinations, etudid, msg, nbabs, nbabsjust, formsemestre_id):
|
||||
"""Actually send the notification by email, and register it in database"""
|
||||
cnx = ndb.GetDBConnexion()
|
||||
log("abs_notify: sending notification to %s" % destinations)
|
||||
cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor)
|
||||
log(f"abs_notify: sending notification to {destinations}")
|
||||
for dest_addr in destinations:
|
||||
msg.recipients = [dest_addr]
|
||||
email.send_message(msg)
|
||||
ndb.SimpleQuery(
|
||||
"""INSERT into absences_notifications
|
||||
(etudid, email, nbabs, nbabsjust, formsemestre_id)
|
||||
VALUES (%(etudid)s, %(email)s, %(nbabs)s, %(nbabsjust)s, %(formsemestre_id)s)
|
||||
""",
|
||||
{
|
||||
"etudid": etudid,
|
||||
"email": dest_addr,
|
||||
"nbabs": nbabs,
|
||||
"nbabsjust": nbabsjust,
|
||||
"formsemestre_id": formsemestre_id,
|
||||
},
|
||||
cursor=cursor,
|
||||
notification = AbsenceNotification(
|
||||
etudid=etudid,
|
||||
email=dest_addr,
|
||||
nbabs=nbabs,
|
||||
nbabsjust=nbabsjust,
|
||||
formsemestre_id=formsemestre_id,
|
||||
)
|
||||
db.session.add(notification)
|
||||
|
||||
logdb(
|
||||
cnx=cnx,
|
||||
Scolog.logdb(
|
||||
method="abs_notify",
|
||||
etudid=etudid,
|
||||
msg="sent to %s (nbabs=%d)" % (destinations, nbabs),
|
||||
msg=f"sent to {destinations} (nbabs={nbabs})",
|
||||
commit=True,
|
||||
)
|
||||
|
||||
|
||||
@ -201,39 +194,32 @@ def abs_notify_is_above_threshold(etudid, nbabs, nbabsjust, formsemestre_id):
|
||||
return False
|
||||
|
||||
|
||||
def etud_nbabs_last_notified(etudid, formsemestre_id=None):
|
||||
def etud_nbabs_last_notified(etudid: int, formsemestre_id: int = None):
|
||||
"""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)"""
|
||||
cnx = ndb.GetDBConnexion()
|
||||
cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor)
|
||||
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(),
|
||||
ou sans semestre (ce dernier cas est nécessaire pour la transition au nouveau code)
|
||||
"""
|
||||
notifications = (
|
||||
AbsenceNotification.query.filter_by(etudid=etudid)
|
||||
.filter(
|
||||
(AbsenceNotification.formsemestre_id == formsemestre_id)
|
||||
| (AbsenceNotification.formsemestre_id.is_(None))
|
||||
)
|
||||
.order_by(AbsenceNotification.notification_date.desc())
|
||||
)
|
||||
res = cursor.dictfetchone()
|
||||
if res:
|
||||
return res["nbabs"]
|
||||
else:
|
||||
return 0
|
||||
last_notif = notifications.first()
|
||||
return last_notif.nbabs if last_notif else 0
|
||||
|
||||
|
||||
def user_nbdays_since_last_notif(email_addr, etudid):
|
||||
def user_nbdays_since_last_notif(email_addr, etudid) -> Optional[int]:
|
||||
"""nb days since last notification to this email, or None if no previous notification"""
|
||||
cnx = ndb.GetDBConnexion()
|
||||
cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor)
|
||||
cursor.execute(
|
||||
"""SELECT * FROM absences_notifications
|
||||
WHERE email = %(email_addr)s and etudid=%(etudid)s
|
||||
ORDER BY notification_date DESC
|
||||
""",
|
||||
{"email_addr": email_addr, "etudid": etudid},
|
||||
)
|
||||
res = cursor.dictfetchone()
|
||||
if res:
|
||||
now = datetime.datetime.now(res["notification_date"].tzinfo)
|
||||
return (now - res["notification_date"]).days
|
||||
else:
|
||||
return None
|
||||
notifications = AbsenceNotification.query.filter_by(
|
||||
etudid=etudid, email=email_addr
|
||||
).order_by(AbsenceNotification.notification_date.desc())
|
||||
last_notif = notifications.first()
|
||||
if last_notif:
|
||||
now = datetime.datetime.now(last_notif.notification_date.tzinfo)
|
||||
return (now - last_notif.notification_date).days
|
||||
return None
|
||||
|
||||
|
||||
def abs_notification_message(
|
||||
@ -264,19 +250,19 @@ def abs_notification_message(
|
||||
log("abs_notification_message: empty template, not sending message")
|
||||
return None
|
||||
|
||||
subject = """[ScoDoc] Trop d'absences pour %(nomprenom)s""" % etud
|
||||
msg = Message(subject, sender=prefs["email_from_addr"])
|
||||
subject = f"""[ScoDoc] Trop d'absences pour {etud["nomprenom"]}"""
|
||||
msg = Message(subject, sender=email.get_from_addr(formsemestre.departement.acronym))
|
||||
msg.body = txt
|
||||
return msg
|
||||
|
||||
|
||||
def retreive_current_formsemestre(etudid: int, cur_date) -> FormSemestre:
|
||||
def retreive_current_formsemestre(etudid: int, cur_date) -> Optional[FormSemestre]:
|
||||
"""Get formsemestre dans lequel etudid est (ou était) inscrit a la date indiquée
|
||||
date est une chaine au format ISO (yyyy-mm-dd)
|
||||
|
||||
Result: FormSemestre ou None si pas inscrit à la date indiquée
|
||||
"""
|
||||
req = """SELECT i.formsemestre_id
|
||||
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)
|
||||
@ -292,9 +278,8 @@ def retreive_current_formsemestre(etudid: int, cur_date) -> FormSemestre:
|
||||
|
||||
def mod_with_evals_at_date(date_abs, etudid):
|
||||
"""Liste des moduleimpls avec des evaluations à la date indiquée"""
|
||||
req = """SELECT m.id AS moduleimpl_id, m.*
|
||||
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
|
||||
AND i.etudid = %(etudid)s AND e.jour = %(date_abs)s"""
|
||||
r = ndb.SimpleDictFetch(req, {"etudid": etudid, "date_abs": date_abs})
|
||||
return r
|
||||
return ndb.SimpleDictFetch(req, {"etudid": etudid, "date_abs": date_abs})
|
||||
|
@ -1080,7 +1080,7 @@ def mail_bulletin(formsemestre_id, infos, pdfdata, filename, recipient_addr):
|
||||
|
||||
subject = f"""Relevé de notes de {etud["nomprenom"]}"""
|
||||
recipients = [recipient_addr]
|
||||
sender = sco_preferences.get_preference("email_from_addr", formsemestre_id)
|
||||
sender = email.get_from_addr()
|
||||
if copy_addr:
|
||||
bcc = copy_addr.strip().split(",")
|
||||
else:
|
||||
|
@ -438,9 +438,7 @@ def notify_etud_change(email_addr, etud, before, after, subject):
|
||||
log("notify_etud_change: sending notification to %s" % email_addr)
|
||||
log("notify_etud_change: subject: %s" % subject)
|
||||
log(txt)
|
||||
email.send_email(
|
||||
subject, sco_preferences.get_preference("email_from_addr"), [email_addr], txt
|
||||
)
|
||||
email.send_email(subject, email.get_from_addr(), [email_addr], txt)
|
||||
return txt
|
||||
|
||||
|
||||
|
@ -308,5 +308,4 @@ Pour plus d'informations sur ce logiciel, voir %s
|
||||
subject = "Mot de passe ScoDoc"
|
||||
else:
|
||||
subject = "Votre accès ScoDoc"
|
||||
sender = sco_preferences.get_preference("email_from_addr")
|
||||
email.send_email(subject, sender, [user["email"]], txt)
|
||||
email.send_email(subject, email.get_from_addr(), [user["email"]], txt)
|
||||
|
@ -360,12 +360,31 @@ class BasePreferences(object):
|
||||
},
|
||||
),
|
||||
# ------------------ MISC
|
||||
(
|
||||
"email_from_addr",
|
||||
{
|
||||
"initvalue": "",
|
||||
"title": "Adresse mail origine",
|
||||
"size": 40,
|
||||
"explanation": """adresse expéditeur pour tous les envois par mails (bulletins,
|
||||
comptes, etc.).
|
||||
Si vide, utilise la config globale.""",
|
||||
"category": "misc",
|
||||
"only_global": True,
|
||||
},
|
||||
),
|
||||
(
|
||||
"use_ue_coefs",
|
||||
{
|
||||
"initvalue": 0,
|
||||
"title": "Utiliser les coefficients d'UE pour calculer la moyenne générale (hors BUT)",
|
||||
"explanation": """Calcule les moyennes dans chaque UE, puis pondère ces résultats pour obtenir la moyenne générale. Par défaut, le coefficient d'une UE est simplement la somme des coefficients des modules dans lesquels l'étudiant a des notes. <b>Attention: changer ce réglage va modifier toutes les moyennes du semestre !</b>. Aucun effet en BUT.""",
|
||||
"title": """Utiliser les coefficients d'UE pour calculer la moyenne générale
|
||||
(hors BUT)""",
|
||||
"explanation": """Calcule les moyennes dans chaque UE, puis pondère ces
|
||||
résultats pour obtenir la moyenne générale.
|
||||
Par défaut, le coefficient d'une UE est simplement la somme des coefficients des modules
|
||||
dans lesquels l'étudiant a des notes. <b>Attention: changer ce réglage va modifier toutes
|
||||
les moyennes du semestre !</b>. Aucun effet en BUT.
|
||||
""",
|
||||
"input_type": "boolcheckbox",
|
||||
"category": "misc",
|
||||
"labels": ["non", "oui"],
|
||||
@ -505,7 +524,7 @@ class BasePreferences(object):
|
||||
{
|
||||
"initvalue": 7,
|
||||
"title": "Fréquence maximale de notification",
|
||||
"explanation": "en jours (pas plus de X envois de mail pour chaque étudiant/destinataire)",
|
||||
"explanation": "nb de jours minimum entre deux mails envoyés au même destinataire à propos d'un même étudiant ",
|
||||
"size": 4,
|
||||
"type": "int",
|
||||
"convert_numbers": True,
|
||||
@ -1569,17 +1588,6 @@ class BasePreferences(object):
|
||||
"category": "bul_mail",
|
||||
},
|
||||
),
|
||||
(
|
||||
"email_from_addr",
|
||||
{
|
||||
"initvalue": current_app.config["SCODOC_MAIL_FROM"],
|
||||
"title": "adresse mail origine",
|
||||
"size": 40,
|
||||
"explanation": "adresse expéditeur pour les envois par mails (bulletins)",
|
||||
"category": "bul_mail",
|
||||
"only_global": True,
|
||||
},
|
||||
),
|
||||
(
|
||||
"bul_intro_mail",
|
||||
{
|
||||
@ -2073,7 +2081,7 @@ class BasePreferences(object):
|
||||
page_title="Préférences",
|
||||
javascripts=["js/detail_summary_persistence.js"],
|
||||
),
|
||||
f"<h2>Préférences globales pour {scu.ScoURL()}</h2>",
|
||||
f"<h2>Préférences globales pour le département {g.scodoc_dept}</h2>",
|
||||
# f"""<p><a href="{url_for("scodoc.configure_logos", scodoc_dept=g.scodoc_dept)
|
||||
# }">modification des logos du département (pour documents pdf)</a></p>"""
|
||||
# if current_user.is_administrator()
|
||||
|
@ -65,7 +65,7 @@
|
||||
<form id="configuration_form_scodoc" class="sco-form" action="" method="post" enctype="multipart/form-data" novalidate>
|
||||
{{ form_scodoc.hidden_tag() }}
|
||||
<div class="row">
|
||||
<div class="col-md-4">
|
||||
<div class="col-md-8">
|
||||
{{ wtf.quick_form(form_scodoc) }}
|
||||
</div>
|
||||
</div>
|
||||
|
@ -48,13 +48,13 @@ from wtforms import HiddenField, PasswordField, StringField, SubmitField
|
||||
from wtforms.validators import DataRequired, Email, ValidationError, EqualTo
|
||||
|
||||
from app import db
|
||||
from app import email
|
||||
from app.auth.forms import DeactivateUserForm
|
||||
from app.auth.models import Permission
|
||||
from app.auth.models import User
|
||||
from app.auth.models import Role
|
||||
from app.auth.models import UserRole
|
||||
from app.auth.models import is_valid_password
|
||||
from app.email import send_email
|
||||
from app.models import Departement
|
||||
from app.models.config import ScoDocSiteConfig
|
||||
|
||||
@ -212,7 +212,6 @@ def create_user_form(user_name=None, edit=0, all_roles=True):
|
||||
user_name = str(user_name)
|
||||
Role.ensure_standard_roles() # assure la présence des rôles en base
|
||||
auth_dept = current_user.dept
|
||||
from_mail = current_app.config["SCODOC_MAIL_FROM"] # current_user.email
|
||||
initvalues = {}
|
||||
edit = int(edit)
|
||||
all_roles = int(all_roles)
|
||||
@ -699,9 +698,10 @@ def create_user_form(user_name=None, edit=0, all_roles=True):
|
||||
token = the_user.get_reset_password_token()
|
||||
else:
|
||||
token = None
|
||||
send_email(
|
||||
# Le from doit utiliser la préférence du département de l'utilisateur
|
||||
email.send_email(
|
||||
"[ScoDoc] Création de votre compte",
|
||||
sender=from_mail, # current_app.config["ADMINS"][0],
|
||||
sender=email.get_from_addr(),
|
||||
recipients=[the_user.email],
|
||||
text_body=render_template(
|
||||
"email/welcome.txt", user=the_user, token=token
|
||||
|
@ -32,7 +32,9 @@ def login():
|
||||
the user's attributes are saved under the key
|
||||
'CAS_USERNAME_ATTRIBUTE_KEY'
|
||||
"""
|
||||
|
||||
if not "CAS_SERVER" in current_app.config:
|
||||
current_app.logger.info("cas_login: no configuration")
|
||||
return "CAS configuration missing"
|
||||
cas_token_session_key = current_app.config["CAS_TOKEN_SESSION_KEY"]
|
||||
|
||||
redirect_url = create_cas_login_url(
|
||||
|
Loading…
Reference in New Issue
Block a user