diff --git a/app/but/bulletin_but.py b/app/but/bulletin_but.py
index 3b90187b91..dcbcd4176c 100644
--- a/app/but/bulletin_but.py
+++ b/app/but/bulletin_but.py
@@ -326,10 +326,7 @@ class BulletinBUT:
semestre_infos["ECTS"] = {"acquis": ects_acquis, "total": ects_tot}
if sco_preferences.get_preference("bul_show_decision", formsemestre.id):
semestre_infos.update(
- sco_bulletins_json.dict_decision_jury(etud.id, formsemestre.id)
- )
- semestre_infos.update(
- but_validations.dict_decision_jury(etud, formsemestre)
+ sco_bulletins_json.dict_decision_jury(etud, formsemestre)
)
if etat_inscription == scu.INSCRIT:
# moyenne des moyennes générales du semestre
diff --git a/app/models/but_validations.py b/app/models/but_validations.py
index 9d42c14d3f..71b5f883dd 100644
--- a/app/models/but_validations.py
+++ b/app/models/but_validations.py
@@ -67,7 +67,7 @@ class ApcValidationRCUE(db.Model):
return self.ue2.niveau_competence
def to_dict_bul(self) -> dict:
- "Export dict pour bulletins"
+ "Export dict pour bulletins: le code et le niveau de compétence"
return {"code": self.code, "niveau": self.niveau().to_dict_bul()}
@@ -309,7 +309,9 @@ class ApcValidationAnnee(db.Model):
def dict_decision_jury(etud: Identite, formsemestre: FormSemestre) -> dict:
"""
- Un dict avec les décisions de jury BUT enregistrées.
+ Un dict avec les décisions de jury BUT enregistrées:
+ - decision_rcue : list[dict]
+ - decision_annee : dict
Ne reprend pas les décisions d'UE, non spécifiques au BUT.
"""
decisions = {}
diff --git a/app/scodoc/sco_bulletins.py b/app/scodoc/sco_bulletins.py
index 4a82674e23..f737c4eec5 100644
--- a/app/scodoc/sco_bulletins.py
+++ b/app/scodoc/sco_bulletins.py
@@ -434,7 +434,7 @@ def _get_etud_etat_html(etat: str) -> str:
elif etat == scu.DEF: # "DEF"
return ' (DEFAILLANT) '
else:
- return ' (%s) ' % etat
+ return f' ({etat}) '
def _sort_mod_by_matiere(modlist, nt, etudid):
@@ -707,6 +707,7 @@ def etud_descr_situation_semestre(
descr_decisions_ue : ' UE acquises: UE1, UE2', ou vide si pas de dec. ou si pas show_uevalid
descr_mention : 'Mention Bien', ou vide si pas de mention ou si pas show_mention
"""
+ # Fonction utilisée par tous les bulletins (APC ou classiques)
cnx = ndb.GetDBConnexion()
infos = scu.DictDefault(defaultvalue="")
@@ -728,8 +729,7 @@ def etud_descr_situation_semestre(
# il y a eu une erreur qui a laissé un event 'inscription'
# on l'efface:
log(
- "etud_descr_situation_semestre: removing duplicate INSCRIPTION event for etudid=%s !"
- % etudid
+ f"etud_descr_situation_semestre: removing duplicate INSCRIPTION event for etudid={etudid} !"
)
sco_etud.scolar_events_delete(cnx, event["event_id"])
else:
@@ -738,8 +738,7 @@ def etud_descr_situation_semestre(
# assert date_dem == None, 'plusieurs démissions !'
if date_dem: # cela ne peut pas arriver sauf bug (signale a Evry 2013?)
log(
- "etud_descr_situation_semestre: removing duplicate DEMISSION event for etudid=%s !"
- % etudid
+ f"etud_descr_situation_semestre: removing duplicate DEMISSION event for etudid={etudid} !"
)
sco_etud.scolar_events_delete(cnx, event["event_id"])
else:
@@ -747,8 +746,7 @@ def etud_descr_situation_semestre(
elif event_type == "DEFAILLANCE":
if date_def:
log(
- "etud_descr_situation_semestre: removing duplicate DEFAILLANCE event for etudid=%s !"
- % etudid
+ f"etud_descr_situation_semestre: removing duplicate DEFAILLANCE event for etudid={etudid} !"
)
sco_etud.scolar_events_delete(cnx, event["event_id"])
else:
@@ -756,10 +754,10 @@ def etud_descr_situation_semestre(
if show_date_inscr:
if not date_inscr:
infos["date_inscription"] = ""
- infos["descr_inscription"] = "Pas inscrit%s." % ne
+ infos["descr_inscription"] = f"Pas inscrit{ne}."
else:
infos["date_inscription"] = date_inscr
- infos["descr_inscription"] = "Inscrit%s le %s." % (ne, date_inscr)
+ infos["descr_inscription"] = f"Inscrit{ne} le {date_inscr}."
else:
infos["date_inscription"] = ""
infos["descr_inscription"] = ""
@@ -767,15 +765,15 @@ def etud_descr_situation_semestre(
infos["situation"] = infos["descr_inscription"]
if date_dem:
- infos["descr_demission"] = "Démission le %s." % date_dem
+ infos["descr_demission"] = f"Démission le {date_dem}."
infos["date_demission"] = date_dem
infos["descr_decision_jury"] = "Démission"
infos["situation"] += " " + infos["descr_demission"]
return infos, None # ne donne pas les dec. de jury pour les demissionnaires
if date_def:
- infos["descr_defaillance"] = "Défaillant%s" % ne
+ infos["descr_defaillance"] = f"Défaillant{ne}"
infos["date_defaillance"] = date_def
- infos["descr_decision_jury"] = "Défaillant%s" % ne
+ infos["descr_decision_jury"] = f"Défaillant{ne}"
infos["situation"] += " " + infos["descr_defaillance"]
dpv = sco_pvjury.dict_pvjury(formsemestre_id, etudids=[etudid])
@@ -798,6 +796,7 @@ def etud_descr_situation_semestre(
dec = infos["descr_decision_jury"]
else:
infos["descr_decision_jury"] = ""
+ infos["decision_jury"] = ""
if pv["decisions_ue_descr"] and show_uevalid:
infos["decisions_ue"] = pv["decisions_ue_descr"]
@@ -809,14 +808,31 @@ def etud_descr_situation_semestre(
infos["mention"] = pv["mention"]
if pv["mention"] and show_mention:
- dec += "Mention " + pv["mention"] + ". "
+ dec += f"Mention {pv['mention']}."
+
+ # Décisions APC / BUT
+ if pv.get("decision_annee", {}):
+ infos["descr_decision_annee"] = "Décision année: " + pv.get(
+ "decision_annee", {}
+ ).get("code", "")
+ else:
+ infos["descr_decision_annee"] = ""
+ if pv.get("decision_rcue", []):
+ infos["descr_decisions_rcue"] = "Niveaux de compétences: " + ", ".join(
+ [
+ f"""{dec_rcue["niveau"]["competence"]["titre"]} {dec_rcue["niveau"]["ordre"]}: {dec_rcue["code"]}"""
+ for dec_rcue in pv.get("decision_rcue", [])
+ ]
+ )
+ else:
+ infos["descr_decisions_rcue"] = ""
infos["situation"] += " " + dec
if not pv["validation_parcours"]: # parcours non terminé
if pv["autorisations_descr"]:
- infos["situation"] += (
- " Autorisé à s'inscrire en %s." % pv["autorisations_descr"]
- )
+ infos[
+ "situation"
+ ] += f" Autorisé à s'inscrire en {pv['autorisations_descr']}."
else:
infos["situation"] += " Diplôme obtenu."
return infos, dpv
diff --git a/app/scodoc/sco_bulletins_json.py b/app/scodoc/sco_bulletins_json.py
index c48c0d4394..3d9a6592c1 100644
--- a/app/scodoc/sco_bulletins_json.py
+++ b/app/scodoc/sco_bulletins_json.py
@@ -25,7 +25,7 @@
#
##############################################################################
-"""Génération du bulletin en format JSON (beta, non completement testé)
+"""Génération du bulletin en format JSON
"""
import datetime
@@ -33,8 +33,9 @@ import json
from app.comp import res_sem
from app.comp.res_compat import NotesTableCompat
-from app.models.formsemestre import FormSemestre
+from app.models import but_validations
from app.models.etudiants import Identite
+from app.models.formsemestre import FormSemestre
import app.scodoc.sco_utils as scu
import app.scodoc.notesdb as ndb
@@ -139,7 +140,7 @@ def formsemestre_bulletinetud_published_dict(
etat_inscription = etud.inscription_etat(formsemestre.id)
if etat_inscription != scu.INSCRIT:
- d.update(dict_decision_jury(etudid, formsemestre_id, with_decisions=True))
+ d.update(dict_decision_jury(etud, formsemestre, with_decisions=True))
return d
# Groupes:
@@ -343,9 +344,7 @@ def formsemestre_bulletinetud_published_dict(
d["absences"] = dict(nbabs=nbabs, nbabsjust=nbabsjust)
# --- Decision Jury
- d.update(
- dict_decision_jury(etudid, formsemestre_id, with_decisions=xml_with_decisions)
- )
+ d.update(dict_decision_jury(etud, formsemestre, with_decisions=xml_with_decisions))
# --- Appreciations
cnx = ndb.GetDBConnexion()
apprecs = sco_etud.appreciations_list(
@@ -364,7 +363,9 @@ def formsemestre_bulletinetud_published_dict(
return d
-def dict_decision_jury(etudid, formsemestre_id, with_decisions=False) -> dict:
+def dict_decision_jury(
+ etud: Identite, formsemestre: FormSemestre, with_decisions: bool = False
+) -> dict:
"""dict avec decision pour bulletins json
- decision : décision semestre
- decision_ue : list des décisions UE
@@ -372,6 +373,8 @@ def dict_decision_jury(etudid, formsemestre_id, with_decisions=False) -> dict:
with_decision donne les décision même si bul_show_decision est faux.
+ Si formation APC, indique aussi validations année et RCUEs
+
Exemple:
{
'autorisation_inscription': [{'semestre_id': 4}],
@@ -397,14 +400,14 @@ def dict_decision_jury(etudid, formsemestre_id, with_decisions=False) -> dict:
d = {}
if (
- sco_preferences.get_preference("bul_show_decision", formsemestre_id)
+ sco_preferences.get_preference("bul_show_decision", formsemestre.id)
or with_decisions
):
infos, dpv = sco_bulletins.etud_descr_situation_semestre(
- etudid,
- formsemestre_id,
+ etud.id,
+ formsemestre.id,
show_uevalid=sco_preferences.get_preference(
- "bul_show_uevalid", formsemestre_id
+ "bul_show_uevalid", formsemestre.id
),
)
d["situation"] = infos["situation"]
@@ -456,4 +459,7 @@ def dict_decision_jury(etudid, formsemestre_id, with_decisions=False) -> dict:
)
else:
d["decision"] = dict(code="", etat="DEM")
+ # Ajout jury BUT:
+ if formsemestre.formation.is_apc():
+ d.update(but_validations.dict_decision_jury(etud, formsemestre))
return d
diff --git a/app/scodoc/sco_bulletins_pdf.py b/app/scodoc/sco_bulletins_pdf.py
index 501bd98ced..3d91beb78f 100644
--- a/app/scodoc/sco_bulletins_pdf.py
+++ b/app/scodoc/sco_bulletins_pdf.py
@@ -140,12 +140,15 @@ def process_field(field, cdict, style, suppress_empty_pars=False, format="pdf"):
text = (field or "") % scu.WrapDict(
cdict
) # note that None values are mapped to empty strings
- except:
+ except: # pylint: disable=bare-except
log(
f"""process_field: invalid format. field={field!r}
values={pprint.pformat(cdict)}
"""
)
+ # ne sera pas visible si lien vers pdf:
+ scu.flash_once(f"Attention: format PDF invalide (champs {field}")
+ raise ValueError
text = (
"format invalide !"
+ traceback.format_exc()
diff --git a/app/scodoc/sco_pdf.py b/app/scodoc/sco_pdf.py
index 2f468ccfc0..3a7d7a553f 100755
--- a/app/scodoc/sco_pdf.py
+++ b/app/scodoc/sco_pdf.py
@@ -55,6 +55,7 @@ from reportlab.lib import styles
from flask import g
+from app.scodoc import sco_utils as scu
from app.scodoc.sco_utils import CONFIG
from app import log
from app.scodoc.sco_exceptions import ScoGenError, ScoValueError
@@ -67,7 +68,7 @@ PAGE_WIDTH = defaultPageSize[0]
DEFAULT_PDF_FOOTER_TEMPLATE = CONFIG.DEFAULT_PDF_FOOTER_TEMPLATE
-def SU(s):
+def SU(s: str) -> str:
"convert s from string to string suitable for ReportLab"
if not s:
return ""
@@ -145,9 +146,9 @@ def makeParas(txt, style, suppress_empty=False):
) from e
else:
raise e
- except Exception as e:
+ except Exception as exc:
log(traceback.format_exc())
- log("Invalid pdf para format: %s" % txt)
+ log(f"Invalid pdf para format: {txt}")
try:
result = [
Paragraph(
@@ -155,13 +156,14 @@ def makeParas(txt, style, suppress_empty=False):
style,
)
]
- except ValueError as e: # probleme font ? essaye sans style
+ except ValueError as exc2: # probleme font ? essaye sans style
# recupere font en cause ?
m = re.match(r".*family/bold/italic for (.*)", e.args[0], re.DOTALL)
if m:
message = f"police non disponible: {m[1]}"
else:
message = "format invalide"
+ scu.flash_once(f"problème génération PDF: {message}")
return [
Paragraph(
SU(f'Erreur: {message}'),
diff --git a/app/scodoc/sco_pvjury.py b/app/scodoc/sco_pvjury.py
index 9975e7c608..019dbfbfa1 100644
--- a/app/scodoc/sco_pvjury.py
+++ b/app/scodoc/sco_pvjury.py
@@ -57,7 +57,13 @@ from flask import g, request
from app.comp import res_sem
from app.comp.res_compat import NotesTableCompat
-from app.models import FormSemestre, UniteEns, ScolarAutorisationInscription
+from app.models import (
+ FormSemestre,
+ UniteEns,
+ ScolarAutorisationInscription,
+ but_validations,
+)
+from app.models.etudiants import Identite
import app.scodoc.sco_utils as scu
import app.scodoc.notesdb as ndb
@@ -81,8 +87,8 @@ from app.scodoc.sco_pdf import PDFLOCK
from app.scodoc.TrivialFormulator import TrivialFormulator
-def _descr_decisions_ues(nt, etudid, decisions_ue, decision_sem):
- """Liste des UE validées dans ce semestre"""
+def _descr_decisions_ues(nt, etudid, decisions_ue, decision_sem) -> list[dict]:
+ """Liste des UE validées dans ce semestre (incluant les UE capitalisées)"""
if not decisions_ue:
return []
uelist = []
@@ -93,14 +99,17 @@ def _descr_decisions_ues(nt, etudid, decisions_ue, decision_sem):
decisions_ue[ue_id]["code"] == sco_codes_parcours.ADM
or (
# XXX ceci devrait dépendre du parcours et non pas être une option ! #sco8
- scu.CONFIG.CAPITALIZE_ALL_UES
+ decision_sem
+ and scu.CONFIG.CAPITALIZE_ALL_UES
and sco_codes_parcours.code_semestre_validant(decision_sem["code"])
)
):
ue = sco_edit_ue.ue_list(args={"ue_id": ue_id})[0]
uelist.append(ue)
except:
- log("descr_decisions_ues: ue_id=%s decisions_ue=%s" % (ue_id, decisions_ue))
+ log(
+ f"Exception in descr_decisions_ues: ue_id={ue_id} decisions_ue={decisions_ue}"
+ )
# Les UE capitalisées dans d'autres semestres:
if etudid in nt.validations.ue_capitalisees.index:
for ue_id in nt.validations.ue_capitalisees.loc[[etudid]]["ue_id"]:
@@ -230,8 +239,11 @@ def dict_pvjury(
L = []
D = {} # même chose que L, mais { etudid : dec }
for etudid in etudids:
- etud = sco_etud.get_etud_info(etudid=etudid, filled=True)[0]
- Se = sco_cursus.get_situation_etud_cursus(etud, formsemestre_id)
+ # etud = sco_etud.get_etud_info(etudid=etudid, filled=True)[0]
+ etud: Identite = Identite.query.get(etudid)
+ Se = sco_cursus.get_situation_etud_cursus(
+ etud.to_dict_scodoc7(), formsemestre_id
+ )
semestre_non_terminal = semestre_non_terminal or Se.semestre_non_terminal
d = {}
d["identite"] = nt.identdict[etudid]
@@ -240,6 +252,8 @@ def dict_pvjury(
) # I|D|DEF (inscription ou démission ou défaillant)
d["decision_sem"] = nt.get_etud_decision_sem(etudid)
d["decisions_ue"] = nt.get_etud_decision_ues(etudid)
+ if formsemestre.formation.is_apc():
+ d.update(but_validations.dict_decision_jury(etud, formsemestre))
d["last_formsemestre_id"] = Se.get_semestres()[
-1
] # id du dernier semestre (chronologiquement) dans lequel il a été inscrit
@@ -305,12 +319,7 @@ def dict_pvjury(
d["decision_sem"]["compense_formsemestre_id"]
)
obs.append(
- "%s compense %s (%s)"
- % (
- sem["sem_id_txt"],
- compensed["sem_id_txt"],
- compensed["anneescolaire"],
- )
+ f"""{sem["sem_id_txt"]} compense {compensed["sem_id_txt"]} ({compensed["anneescolaire"]})"""
)
d["observation"] = ", ".join(obs)
diff --git a/app/scodoc/sco_utils.py b/app/scodoc/sco_utils.py
index 31de4bb228..2087a141e8 100644
--- a/app/scodoc/sco_utils.py
+++ b/app/scodoc/sco_utils.py
@@ -664,6 +664,15 @@ def flash_errors(form):
# see https://getbootstrap.com/docs/4.0/components/alerts/
+def flash_once(message: str):
+ """Flash the message, but only once per request"""
+ if not hasattr(g, "sco_flashed_once"):
+ g.sco_flashed_once = set()
+ if not message in g.sco_flashed_once:
+ flash(message)
+ g.sco_flashed_once.add(message)
+
+
def sendCSVFile(data, filename): # DEPRECATED utiliser send_file
"""publication fichier CSV."""
return send_file(data, filename=filename, mime=CSV_MIMETYPE, attached=True)
diff --git a/app/views/notes.py b/app/views/notes.py
index 4485c1d5c1..7bb9b027c5 100644
--- a/app/views/notes.py
+++ b/app/views/notes.py
@@ -2552,9 +2552,8 @@ def formsemestre_validation_suppress_etud(
)
if not dialog_confirmed:
d = sco_bulletins_json.dict_decision_jury(
- etudid, formsemestre_id, with_decisions=True
+ etud, formsemestre, with_decisions=True
)
- d.update(but_validations.dict_decision_jury(etud, formsemestre))
descr_ues = [f"{u['acronyme']}: {u['code']}" for u in d.get("decision_ue", [])]
dec_annee = d.get("decision_annee")
diff --git a/sco_version.py b/sco_version.py
index 256794b73b..e635062a04 100644
--- a/sco_version.py
+++ b/sco_version.py
@@ -1,7 +1,7 @@
# -*- mode: python -*-
# -*- coding: utf-8 -*-
-SCOVERSION = "9.3.16"
+SCOVERSION = "9.3.17"
SCONAME = "ScoDoc"