diff --git a/app/but/bulletin_but.py b/app/but/bulletin_but.py index 3b90187b..dcbcd417 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 9d42c14d..71b5f883 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 4a82674e..f737c4ee 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 c48c0d43..3d9a6592 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 501bd98c..3d91beb7 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 2f468ccf..3a7d7a55 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 9975e7c6..019dbfbf 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 31de4bb2..2087a141 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 4485c1d5..7bb9b027 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 256794b7..e635062a 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"