Merge branch 'master' of https://scodoc.org/git/viennet/ScoDoc into dev92
This commit is contained in:
commit
ec9cdfe50a
@ -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
|
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -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"""
|
||||||
|
65
app/email.py
65
app/email.py
@ -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()
|
||||||
|
@ -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,
|
||||||
|
@ -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:
|
||||||
|
@ -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(
|
||||||
|
@ -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])
|
||||||
|
@ -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"
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user