From bcbace01208bfc91680b4406b503d645f1db9481 Mon Sep 17 00:00:00 2001 From: Emmanuel Viennet Date: Thu, 3 Mar 2022 23:02:24 +0100 Subject: [PATCH 1/4] N'affiche pas les UE sans inscriptions sur les buleltins classiques --- app/__init__.py | 4 ++-- app/comp/res_classic.py | 7 ++++++- app/scodoc/sco_bulletins.py | 4 +++- 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/app/__init__.py b/app/__init__.py index 76f9471bb4..237f53ceac 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -201,7 +201,7 @@ def create_app(config_class=DevConfig): app.register_blueprint(auth_bp, url_prefix="/auth") from app.entreprises import bp as entreprises_bp - + app.register_blueprint(entreprises_bp, url_prefix="/ScoDoc/entreprises") from app.views import scodoc_bp @@ -297,7 +297,7 @@ def create_app(config_class=DevConfig): from app.scodoc.sco_bulletins_standard import BulletinGeneratorStandard from app.scodoc.sco_bulletins_ucac import BulletinGeneratorUCAC - # l'ordre est important, le premeir sera le "défaut" pour les nouveaux départements. + # l'ordre est important, le premier sera le "défaut" pour les nouveaux départements. sco_bulletins_generator.register_bulletin_class(BulletinGeneratorStandard) sco_bulletins_generator.register_bulletin_class(BulletinGeneratorLegacy) sco_bulletins_generator.register_bulletin_class(BulletinGeneratorUCAC) 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/scodoc/sco_bulletins.py b/app/scodoc/sco_bulletins.py index 47947bd361..5f6f91e5b8 100644 --- a/app/scodoc/sco_bulletins.py +++ b/app/scodoc/sco_bulletins.py @@ -441,7 +441,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). From e04a187a01009c35facc7e9561020498cb85845c Mon Sep 17 00:00:00 2001 From: Emmanuel Viennet Date: Fri, 4 Mar 2022 18:55:45 +0100 Subject: [PATCH 2/4] Exception si archive introuvable --- app/scodoc/sco_archives.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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: From b4a7749e5ae8b1a75ab44fbacf134865022caa8d Mon Sep 17 00:00:00 2001 From: Emmanuel Viennet Date: Fri, 4 Mar 2022 20:02:50 +0100 Subject: [PATCH 3/4] Mode test pour les mails. Closes #326 --- app/__init__.py | 23 ++++++------- app/email.py | 65 ++++++++++++++++++++++++++++++++--- app/scodoc/html_sco_header.py | 5 +-- app/scodoc/sco_bulletins.py | 14 +++++--- app/scodoc/sco_preferences.py | 14 ++++++++ sco_version.py | 2 +- 6 files changed, 98 insertions(+), 25 deletions(-) diff --git a/app/__init__.py b/app/__init__.py index 237f53ceac..74585a7b2a 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 @@ -457,15 +457,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/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_bulletins.py b/app/scodoc/sco_bulletins.py index 5f6f91e5b8..bb0597e598 100644 --- a/app/scodoc/sco_bulletins.py +++ b/app/scodoc/sco_bulletins.py @@ -1040,13 +1040,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 aab148e707..7476be6b9b 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 55ce771df6..4ab7caa51c 100644 --- a/sco_version.py +++ b/sco_version.py @@ -1,7 +1,7 @@ # -*- mode: python -*- # -*- coding: utf-8 -*- -SCOVERSION = "9.1.71" +SCOVERSION = "9.1.72" SCONAME = "ScoDoc" From 3d0509de64d143eb5c53d7707e65668cbc28651c Mon Sep 17 00:00:00 2001 From: Emmanuel Viennet Date: Fri, 4 Mar 2022 20:51:44 +0100 Subject: [PATCH 4/4] =?UTF-8?q?Bonus=20Saint-Brieuc=20(=C3=A0=20valider:?= =?UTF-8?q?=20semestres=20=C3=A0=20affecter=3F)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/comp/bonus_spo.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) 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: +
    +
  • Bonus = (S - 10)/20
  • +
+
(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