Merge branch 'master' of https://scodoc.org/git/viennet/ScoDoc into dev92

This commit is contained in:
Emmanuel Viennet 2022-03-04 20:59:04 +01:00
commit ec9cdfe50a
9 changed files with 130 additions and 29 deletions

View File

@ -13,7 +13,7 @@ from logging.handlers import SMTPHandler, WatchedFileHandler
from flask import current_app, g, request from flask import current_app, g, request
from flask import Flask 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 import render_template
from flask.logging import default_handler from flask.logging import default_handler
from flask_sqlalchemy import SQLAlchemy 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 notesdb as ndb, sco_preferences
from app.scodoc import sco_cache from app.scodoc import sco_cache
# admin_role = Role.query.filter_by(name="SuperAdmin").first()
# if admin_role: def scodoc_flash_status_messages():
# admin = ( """Should be called on each page: flash messages indicating specific ScoDoc status"""
# User.query.join(UserRole) email_test_mode_address = sco_preferences.get_preference("email_test_mode_address")
# .filter((UserRole.user_id == User.id) & (UserRole.role_id == admin_role.id)) if email_test_mode_address:
# .first() flash(
# ) f"Mode test: mails redirigés vers {email_test_mode_address}",
# else: category="warning",
# click.echo( )
# "Warning: user database not initialized !\n (use: flask user-db-init)"
# )
# admin = None

View File

@ -824,6 +824,27 @@ class BonusRoanne(BonusSportAdditif):
proportion_point = 1 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:
<ul>
<li>Bonus = (S - 10)/20</li>
</ul>
<div class="warning">(XXX vérifier si S6 est éligible au bonus, et le S2 du DUT XXX)</div>
"""
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): class BonusStDenis(BonusSportAdditif):
"""Calcul bonus modules optionnels (sport, culture), règle IUT Saint-Denis """Calcul bonus modules optionnels (sport, culture), règle IUT Saint-Denis

View File

@ -146,7 +146,12 @@ class ResultatsSemestreClassic(NotesTableCompat):
"""La moyenne de l'étudiant dans le moduleimpl """La moyenne de l'étudiant dans le moduleimpl
Result: valeur float (peut être NaN) ou chaîne "NI" (non inscrit ou DEM) 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: def get_mod_stats(self, moduleimpl_id: int) -> dict:
"""Stats sur les notes obtenues dans un modimpl""" """Stats sur les notes obtenues dans un modimpl"""

View File

@ -1,8 +1,17 @@
# -*- coding: UTF-8 -* # -*- coding: UTF-8 -*
##############################################################################
# ScoDoc
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
# See LICENSE
##############################################################################
from threading import Thread from threading import Thread
from flask import current_app
from flask import current_app, g
from flask_mail import Message from flask_mail import Message
from app import mail from app import mail
from app.scodoc import sco_preferences
def send_async_email(app, msg): def send_async_email(app, msg):
@ -11,20 +20,66 @@ def send_async_email(app, msg):
def send_email( 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, If html_body is specified, build a multipart message with HTML content,
else send a plain text email. 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.body = text_body
msg.html = html_body msg.html = html_body
if attachments:
for attachment in attachments:
msg.attach(
attachment["filename"], attachment["mimetype"], attachment["data"]
)
send_message(msg) 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( Thread(
target=send_async_email, args=(current_app._get_current_object(), msg) target=send_async_email, args=(current_app._get_current_object(), msg)
).start() ).start()

View File

@ -35,7 +35,7 @@ from flask import request
from flask_login import current_user from flask_login import current_user
import app.scodoc.sco_utils as scu import app.scodoc.sco_utils as scu
from app import log from app import scodoc_flash_status_messages
from app.scodoc import html_sidebar from app.scodoc import html_sidebar
import sco_version import sco_version
@ -153,13 +153,14 @@ def sco_header(
"Main HTML page header for ScoDoc" "Main HTML page header for ScoDoc"
from app.scodoc.sco_formsemestre_status import formsemestre_page_title from app.scodoc.sco_formsemestre_status import formsemestre_page_title
scodoc_flash_status_messages()
# Get head message from http request: # Get head message from http request:
if not head_message: if not head_message:
if request.method == "POST": if request.method == "POST":
head_message = request.form.get("head_message", "") head_message = request.form.get("head_message", "")
elif request.method == "GET": elif request.method == "GET":
head_message = request.args.get("head_message", "") head_message = request.args.get("head_message", "")
params = { params = {
"page_title": page_title or sco_version.SCONAME, "page_title": page_title or sco_version.SCONAME,
"no_side_bar": no_side_bar, "no_side_bar": no_side_bar,

View File

@ -70,13 +70,13 @@ from app.scodoc.sco_exceptions import (
) )
from app.scodoc import html_sco_header from app.scodoc import html_sco_header
from app.scodoc import sco_bulletins_pdf from app.scodoc import sco_bulletins_pdf
from app.scodoc import sco_excel
from app.scodoc import sco_formsemestre from app.scodoc import sco_formsemestre
from app.scodoc import sco_groups from app.scodoc import sco_groups
from app.scodoc import sco_groups_view from app.scodoc import sco_groups_view
from app.scodoc import sco_permissions_check from app.scodoc import sco_permissions_check
from app.scodoc import sco_pvjury from app.scodoc import sco_pvjury
from app.scodoc import sco_pvpdf from app.scodoc import sco_pvpdf
from app.scodoc.sco_exceptions import ScoValueError
class BaseArchiver(object): class BaseArchiver(object):
@ -254,7 +254,7 @@ class BaseArchiver(object):
self.initialize() self.initialize()
if not scu.is_valid_filename(filename): if not scu.is_valid_filename(filename):
log('Archiver.get: invalid filename "%s"' % 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) fname = os.path.join(archive_id, filename)
log("reading archive file %s" % fname) log("reading archive file %s" % fname)
with open(fname, "rb") as f: with open(fname, "rb") as f:

View File

@ -433,7 +433,9 @@ def _sort_mod_by_matiere(modlist, nt, etudid):
return matmod 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 """Infos sur les modules (et évaluations) dans une UE
(ajoute les informations aux modimpls) (ajoute les informations aux modimpls)
Result: liste de modules de l'UE avec les infos dans chacun (seulement ceux où l'étudiant est inscrit). 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() bcc = copy_addr.strip()
else: else:
bcc = "" bcc = ""
msg = Message(subject, sender=sender, recipients=recipients, bcc=[bcc])
msg.body = hea
# Attach pdf # Attach pdf
msg.attach(filename, scu.PDF_MIMETYPE, pdfdata)
log("mail bulletin a %s" % recipient_addr) 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( def _formsemestre_bulletinetud_header_html(

View File

@ -245,6 +245,7 @@ PREF_CATEGORIES = (
), ),
("pe", {"title": "Avis de poursuites d'études"}), ("pe", {"title": "Avis de poursuites d'études"}),
("edt", {"title": "Connexion avec le logiciel d'emplois du temps"}), ("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", "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]) self.prefs_name = set([x[0] for x in self.prefs_definition])

View File

@ -1,7 +1,7 @@
# -*- mode: python -*- # -*- mode: python -*-
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
SCOVERSION = "9.2a-71" SCOVERSION = "9.2a-72"
SCONAME = "ScoDoc" SCONAME = "ScoDoc"