diff --git a/app/__init__.py b/app/__init__.py
index 905cfce1ab..db5a15b089 100644
--- a/app/__init__.py
+++ b/app/__init__.py
@@ -13,7 +13,7 @@ from logging.handlers import SMTPHandler, WatchedFileHandler
from flask import current_app, g, request
from flask import Flask
-from flask import abort, has_request_context, jsonify
+from flask import abort, flash, has_request_context, jsonify
from flask import render_template
from flask.logging import default_handler
from flask_sqlalchemy import SQLAlchemy
@@ -459,15 +459,12 @@ from app.models import Departement
from app.scodoc import notesdb as ndb, sco_preferences
from app.scodoc import sco_cache
-# admin_role = Role.query.filter_by(name="SuperAdmin").first()
-# if admin_role:
-# admin = (
-# User.query.join(UserRole)
-# .filter((UserRole.user_id == User.id) & (UserRole.role_id == admin_role.id))
-# .first()
-# )
-# else:
-# click.echo(
-# "Warning: user database not initialized !\n (use: flask user-db-init)"
-# )
-# admin = None
+
+def scodoc_flash_status_messages():
+ """Should be called on each page: flash messages indicating specific ScoDoc status"""
+ email_test_mode_address = sco_preferences.get_preference("email_test_mode_address")
+ if email_test_mode_address:
+ flash(
+ f"Mode test: mails redirigés vers {email_test_mode_address}",
+ category="warning",
+ )
diff --git a/app/comp/bonus_spo.py b/app/comp/bonus_spo.py
index b272194139..2feb05776e 100644
--- a/app/comp/bonus_spo.py
+++ b/app/comp/bonus_spo.py
@@ -824,6 +824,27 @@ class BonusRoanne(BonusSportAdditif):
proportion_point = 1
+class BonusStBrieuc(BonusSportAdditif):
+ """IUT de Saint Brieuc
+
+ Ne s'applique qu'aux semestres pairs (S2, S4, S6), et bonifie les moyennes d'UE:
+
+ (XXX vérifier si S6 est éligible au bonus, et le S2 du DUT XXX)
+ """
+
+ name = "bonus_iut_stbrieuc"
+ displayed_name = "IUT de Saint-Brieuc"
+ proportion_point = 1 / 20.0
+ classic_use_bonus_ues = True
+
+ def compute_bonus(self, sem_modimpl_moys_inscrits, modimpl_coefs_etuds_no_nan):
+ """calcul du bonus"""
+ if self.formsemestre.semestre_id % 2 == 0:
+ super().compute_bonus(sem_modimpl_moys_inscrits, modimpl_coefs_etuds_no_nan)
+
+
class BonusStDenis(BonusSportAdditif):
"""Calcul bonus modules optionnels (sport, culture), règle IUT Saint-Denis
diff --git a/app/comp/res_classic.py b/app/comp/res_classic.py
index b36eaaf6c8..91614935dc 100644
--- a/app/comp/res_classic.py
+++ b/app/comp/res_classic.py
@@ -146,7 +146,12 @@ class ResultatsSemestreClassic(NotesTableCompat):
"""La moyenne de l'étudiant dans le moduleimpl
Result: valeur float (peut être NaN) ou chaîne "NI" (non inscrit ou DEM)
"""
- return self.modimpls_results[moduleimpl_id].etuds_moy_module.get(etudid, "NI")
+ try:
+ if self.modimpl_inscr_df[moduleimpl_id][etudid]:
+ return self.modimpls_results[moduleimpl_id].etuds_moy_module[etudid]
+ except KeyError:
+ pass
+ return "NI"
def get_mod_stats(self, moduleimpl_id: int) -> dict:
"""Stats sur les notes obtenues dans un modimpl"""
diff --git a/app/email.py b/app/email.py
index 226429df24..1fc7632b66 100644
--- a/app/email.py
+++ b/app/email.py
@@ -1,8 +1,17 @@
# -*- coding: UTF-8 -*
+##############################################################################
+# ScoDoc
+# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
+# See LICENSE
+##############################################################################
+
from threading import Thread
-from flask import current_app
+
+from flask import current_app, g
from flask_mail import Message
+
from app import mail
+from app.scodoc import sco_preferences
def send_async_email(app, msg):
@@ -11,20 +20,66 @@ def send_async_email(app, msg):
def send_email(
- subject: str, sender: str, recipients: list, text_body: str, html_body=""
+ subject: str,
+ sender: str,
+ recipients: list,
+ text_body: str,
+ html_body="",
+ bcc=(),
+ attachments=(),
):
"""
- Send an email
+ Send an email. _All_ ScoDoc mails SHOULD be sent using this function.
+
If html_body is specified, build a multipart message with HTML content,
else send a plain text email.
+
+ attachements: list of dict { 'filename', 'mimetype', 'data' }
"""
- msg = Message(subject, sender=sender, recipients=recipients)
+ msg = Message(subject, sender=sender, recipients=recipients, bcc=bcc)
msg.body = text_body
msg.html = html_body
+ if attachments:
+ for attachment in attachments:
+ msg.attach(
+ attachment["filename"], attachment["mimetype"], attachment["data"]
+ )
+
send_message(msg)
-def send_message(msg):
+def send_message(msg: Message):
+ """Send a message.
+ All ScoDoc emails MUST be sent by this function.
+
+ In mail debug mode, addresses are discarded and all mails are sent to the
+ specified debugging address.
+ """
+ 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(
+ "email_test_mode_address"
+ )
+ if email_test_mode_address:
+ # Mode spécial test: remplace les adresses de destination
+ orig_to = msg.recipients
+ orig_cc = msg.cc
+ orig_bcc = msg.bcc
+ msg.recipients = [email_test_mode_address]
+ msg.cc = None
+ msg.bcc = None
+ msg.subject = "[TEST SCODOC] " + msg.subject
+ msg.body = (
+ f"""--- Message ScoDoc dérouté pour tests ---
+Adresses d'origine:
+ to : {orig_to}
+ cc : {orig_cc}
+ bcc: {orig_bcc}
+---
+ \n\n"""
+ + msg.body
+ )
+
Thread(
target=send_async_email, args=(current_app._get_current_object(), msg)
).start()
diff --git a/app/scodoc/html_sco_header.py b/app/scodoc/html_sco_header.py
index 653cdb80dc..8ce3d2cdd7 100644
--- a/app/scodoc/html_sco_header.py
+++ b/app/scodoc/html_sco_header.py
@@ -35,7 +35,7 @@ from flask import request
from flask_login import current_user
import app.scodoc.sco_utils as scu
-from app import log
+from app import scodoc_flash_status_messages
from app.scodoc import html_sidebar
import sco_version
@@ -153,13 +153,14 @@ def sco_header(
"Main HTML page header for ScoDoc"
from app.scodoc.sco_formsemestre_status import formsemestre_page_title
+ scodoc_flash_status_messages()
+
# Get head message from http request:
if not head_message:
if request.method == "POST":
head_message = request.form.get("head_message", "")
elif request.method == "GET":
head_message = request.args.get("head_message", "")
-
params = {
"page_title": page_title or sco_version.SCONAME,
"no_side_bar": no_side_bar,
diff --git a/app/scodoc/sco_archives.py b/app/scodoc/sco_archives.py
index 159d3305a7..0103caa240 100644
--- a/app/scodoc/sco_archives.py
+++ b/app/scodoc/sco_archives.py
@@ -70,13 +70,13 @@ from app.scodoc.sco_exceptions import (
)
from app.scodoc import html_sco_header
from app.scodoc import sco_bulletins_pdf
-from app.scodoc import sco_excel
from app.scodoc import sco_formsemestre
from app.scodoc import sco_groups
from app.scodoc import sco_groups_view
from app.scodoc import sco_permissions_check
from app.scodoc import sco_pvjury
from app.scodoc import sco_pvpdf
+from app.scodoc.sco_exceptions import ScoValueError
class BaseArchiver(object):
@@ -254,7 +254,7 @@ class BaseArchiver(object):
self.initialize()
if not scu.is_valid_filename(filename):
log('Archiver.get: invalid filename "%s"' % filename)
- raise ValueError("invalid filename")
+ raise ScoValueError("archive introuvable (déjà supprimée ?)")
fname = os.path.join(archive_id, filename)
log("reading archive file %s" % fname)
with open(fname, "rb") as f:
diff --git a/app/scodoc/sco_bulletins.py b/app/scodoc/sco_bulletins.py
index 6db18f9121..60cf4931d3 100644
--- a/app/scodoc/sco_bulletins.py
+++ b/app/scodoc/sco_bulletins.py
@@ -433,7 +433,9 @@ def _sort_mod_by_matiere(modlist, nt, etudid):
return matmod
-def _ue_mod_bulletin(etudid, formsemestre_id, ue_id, modimpls, nt, version):
+def _ue_mod_bulletin(
+ etudid, formsemestre_id, ue_id, modimpls, nt: NotesTableCompat, version
+):
"""Infos sur les modules (et évaluations) dans une UE
(ajoute les informations aux modimpls)
Result: liste de modules de l'UE avec les infos dans chacun (seulement ceux où l'étudiant est inscrit).
@@ -1043,13 +1045,19 @@ def mail_bulletin(formsemestre_id, I, pdfdata, filename, recipient_addr):
bcc = copy_addr.strip()
else:
bcc = ""
- msg = Message(subject, sender=sender, recipients=recipients, bcc=[bcc])
- msg.body = hea
# Attach pdf
- msg.attach(filename, scu.PDF_MIMETYPE, pdfdata)
log("mail bulletin a %s" % recipient_addr)
- email.send_message(msg)
+ email.send_email(
+ subject,
+ sender,
+ recipients,
+ bcc=[bcc],
+ text_body=hea,
+ attachments=[
+ {"filename": filename, "mimetype": scu.PDF_MIMETYPE, "data": pdfdata}
+ ],
+ )
def _formsemestre_bulletinetud_header_html(
diff --git a/app/scodoc/sco_preferences.py b/app/scodoc/sco_preferences.py
index ce73420dd3..b320c164e5 100644
--- a/app/scodoc/sco_preferences.py
+++ b/app/scodoc/sco_preferences.py
@@ -245,6 +245,7 @@ PREF_CATEGORIES = (
),
("pe", {"title": "Avis de poursuites d'études"}),
("edt", {"title": "Connexion avec le logiciel d'emplois du temps"}),
+ ("debug", {"title": "Tests / mise au point"}),
)
@@ -1859,6 +1860,19 @@ class BasePreferences(object):
"category": "edt",
},
),
+ (
+ "email_test_mode_address",
+ {
+ "title": "Adresse de test",
+ "initvalue": "",
+ "explanation": """si cette adresse est indiquée, TOUS les mails
+ envoyés par ScoDoc de ce département vont aller vers elle
+ AU LIEU DE LEUR DESTINATION NORMALE !""",
+ "size": 30,
+ "category": "debug",
+ "only_global": True,
+ },
+ ),
)
self.prefs_name = set([x[0] for x in self.prefs_definition])
diff --git a/sco_version.py b/sco_version.py
index 50fc9f77da..fb7cc3d04b 100644
--- a/sco_version.py
+++ b/sco_version.py
@@ -1,7 +1,7 @@
# -*- mode: python -*-
# -*- coding: utf-8 -*-
-SCOVERSION = "9.2a-71"
+SCOVERSION = "9.2a-72"
SCONAME = "ScoDoc"