Bulletins et PV: champs décisions jury BUT

This commit is contained in:
Emmanuel Viennet 2022-07-08 18:09:45 +02:00
parent 8e463e5971
commit bf27fcbdc6
10 changed files with 97 additions and 54 deletions

View File

@ -326,10 +326,7 @@ class BulletinBUT:
semestre_infos["ECTS"] = {"acquis": ects_acquis, "total": ects_tot} semestre_infos["ECTS"] = {"acquis": ects_acquis, "total": ects_tot}
if sco_preferences.get_preference("bul_show_decision", formsemestre.id): if sco_preferences.get_preference("bul_show_decision", formsemestre.id):
semestre_infos.update( semestre_infos.update(
sco_bulletins_json.dict_decision_jury(etud.id, formsemestre.id) sco_bulletins_json.dict_decision_jury(etud, formsemestre)
)
semestre_infos.update(
but_validations.dict_decision_jury(etud, formsemestre)
) )
if etat_inscription == scu.INSCRIT: if etat_inscription == scu.INSCRIT:
# moyenne des moyennes générales du semestre # moyenne des moyennes générales du semestre

View File

@ -67,7 +67,7 @@ class ApcValidationRCUE(db.Model):
return self.ue2.niveau_competence return self.ue2.niveau_competence
def to_dict_bul(self) -> dict: 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()} 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: 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. Ne reprend pas les décisions d'UE, non spécifiques au BUT.
""" """
decisions = {} decisions = {}

View File

@ -434,7 +434,7 @@ def _get_etud_etat_html(etat: str) -> str:
elif etat == scu.DEF: # "DEF" elif etat == scu.DEF: # "DEF"
return ' <font color="red">(DEFAILLANT)</font> ' return ' <font color="red">(DEFAILLANT)</font> '
else: else:
return ' <font color="red">(%s)</font> ' % etat return f' <font color="red">({etat})</font> '
def _sort_mod_by_matiere(modlist, nt, etudid): 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_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 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() cnx = ndb.GetDBConnexion()
infos = scu.DictDefault(defaultvalue="") 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' # il y a eu une erreur qui a laissé un event 'inscription'
# on l'efface: # on l'efface:
log( log(
"etud_descr_situation_semestre: removing duplicate INSCRIPTION event for etudid=%s !" f"etud_descr_situation_semestre: removing duplicate INSCRIPTION event for etudid={etudid} !"
% etudid
) )
sco_etud.scolar_events_delete(cnx, event["event_id"]) sco_etud.scolar_events_delete(cnx, event["event_id"])
else: else:
@ -738,8 +738,7 @@ def etud_descr_situation_semestre(
# assert date_dem == None, 'plusieurs démissions !' # assert date_dem == None, 'plusieurs démissions !'
if date_dem: # cela ne peut pas arriver sauf bug (signale a Evry 2013?) if date_dem: # cela ne peut pas arriver sauf bug (signale a Evry 2013?)
log( log(
"etud_descr_situation_semestre: removing duplicate DEMISSION event for etudid=%s !" f"etud_descr_situation_semestre: removing duplicate DEMISSION event for etudid={etudid} !"
% etudid
) )
sco_etud.scolar_events_delete(cnx, event["event_id"]) sco_etud.scolar_events_delete(cnx, event["event_id"])
else: else:
@ -747,8 +746,7 @@ def etud_descr_situation_semestre(
elif event_type == "DEFAILLANCE": elif event_type == "DEFAILLANCE":
if date_def: if date_def:
log( log(
"etud_descr_situation_semestre: removing duplicate DEFAILLANCE event for etudid=%s !" f"etud_descr_situation_semestre: removing duplicate DEFAILLANCE event for etudid={etudid} !"
% etudid
) )
sco_etud.scolar_events_delete(cnx, event["event_id"]) sco_etud.scolar_events_delete(cnx, event["event_id"])
else: else:
@ -756,10 +754,10 @@ def etud_descr_situation_semestre(
if show_date_inscr: if show_date_inscr:
if not date_inscr: if not date_inscr:
infos["date_inscription"] = "" infos["date_inscription"] = ""
infos["descr_inscription"] = "Pas inscrit%s." % ne infos["descr_inscription"] = f"Pas inscrit{ne}."
else: else:
infos["date_inscription"] = date_inscr 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: else:
infos["date_inscription"] = "" infos["date_inscription"] = ""
infos["descr_inscription"] = "" infos["descr_inscription"] = ""
@ -767,15 +765,15 @@ def etud_descr_situation_semestre(
infos["situation"] = infos["descr_inscription"] infos["situation"] = infos["descr_inscription"]
if date_dem: 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["date_demission"] = date_dem
infos["descr_decision_jury"] = "Démission" infos["descr_decision_jury"] = "Démission"
infos["situation"] += " " + infos["descr_demission"] infos["situation"] += " " + infos["descr_demission"]
return infos, None # ne donne pas les dec. de jury pour les demissionnaires return infos, None # ne donne pas les dec. de jury pour les demissionnaires
if date_def: if date_def:
infos["descr_defaillance"] = "Défaillant%s" % ne infos["descr_defaillance"] = f"Défaillant{ne}"
infos["date_defaillance"] = date_def 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"] infos["situation"] += " " + infos["descr_defaillance"]
dpv = sco_pvjury.dict_pvjury(formsemestre_id, etudids=[etudid]) dpv = sco_pvjury.dict_pvjury(formsemestre_id, etudids=[etudid])
@ -798,6 +796,7 @@ def etud_descr_situation_semestre(
dec = infos["descr_decision_jury"] dec = infos["descr_decision_jury"]
else: else:
infos["descr_decision_jury"] = "" infos["descr_decision_jury"] = ""
infos["decision_jury"] = ""
if pv["decisions_ue_descr"] and show_uevalid: if pv["decisions_ue_descr"] and show_uevalid:
infos["decisions_ue"] = pv["decisions_ue_descr"] infos["decisions_ue"] = pv["decisions_ue_descr"]
@ -809,14 +808,31 @@ def etud_descr_situation_semestre(
infos["mention"] = pv["mention"] infos["mention"] = pv["mention"]
if pv["mention"] and show_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 infos["situation"] += " " + dec
if not pv["validation_parcours"]: # parcours non terminé if not pv["validation_parcours"]: # parcours non terminé
if pv["autorisations_descr"]: if pv["autorisations_descr"]:
infos["situation"] += ( infos[
" Autorisé à s'inscrire en %s." % pv["autorisations_descr"] "situation"
) ] += f" Autorisé à s'inscrire en {pv['autorisations_descr']}."
else: else:
infos["situation"] += " Diplôme obtenu." infos["situation"] += " Diplôme obtenu."
return infos, dpv return infos, dpv

View File

@ -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 import datetime
@ -33,8 +33,9 @@ import json
from app.comp import res_sem from app.comp import res_sem
from app.comp.res_compat import NotesTableCompat 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.etudiants import Identite
from app.models.formsemestre import FormSemestre
import app.scodoc.sco_utils as scu import app.scodoc.sco_utils as scu
import app.scodoc.notesdb as ndb import app.scodoc.notesdb as ndb
@ -139,7 +140,7 @@ def formsemestre_bulletinetud_published_dict(
etat_inscription = etud.inscription_etat(formsemestre.id) etat_inscription = etud.inscription_etat(formsemestre.id)
if etat_inscription != scu.INSCRIT: 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 return d
# Groupes: # Groupes:
@ -343,9 +344,7 @@ def formsemestre_bulletinetud_published_dict(
d["absences"] = dict(nbabs=nbabs, nbabsjust=nbabsjust) d["absences"] = dict(nbabs=nbabs, nbabsjust=nbabsjust)
# --- Decision Jury # --- Decision Jury
d.update( d.update(dict_decision_jury(etud, formsemestre, with_decisions=xml_with_decisions))
dict_decision_jury(etudid, formsemestre_id, with_decisions=xml_with_decisions)
)
# --- Appreciations # --- Appreciations
cnx = ndb.GetDBConnexion() cnx = ndb.GetDBConnexion()
apprecs = sco_etud.appreciations_list( apprecs = sco_etud.appreciations_list(
@ -364,7 +363,9 @@ def formsemestre_bulletinetud_published_dict(
return d 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 """dict avec decision pour bulletins json
- decision : décision semestre - decision : décision semestre
- decision_ue : list des décisions UE - 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. 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: Exemple:
{ {
'autorisation_inscription': [{'semestre_id': 4}], 'autorisation_inscription': [{'semestre_id': 4}],
@ -397,14 +400,14 @@ def dict_decision_jury(etudid, formsemestre_id, with_decisions=False) -> dict:
d = {} d = {}
if ( if (
sco_preferences.get_preference("bul_show_decision", formsemestre_id) sco_preferences.get_preference("bul_show_decision", formsemestre.id)
or with_decisions or with_decisions
): ):
infos, dpv = sco_bulletins.etud_descr_situation_semestre( infos, dpv = sco_bulletins.etud_descr_situation_semestre(
etudid, etud.id,
formsemestre_id, formsemestre.id,
show_uevalid=sco_preferences.get_preference( show_uevalid=sco_preferences.get_preference(
"bul_show_uevalid", formsemestre_id "bul_show_uevalid", formsemestre.id
), ),
) )
d["situation"] = infos["situation"] d["situation"] = infos["situation"]
@ -456,4 +459,7 @@ def dict_decision_jury(etudid, formsemestre_id, with_decisions=False) -> dict:
) )
else: else:
d["decision"] = dict(code="", etat="DEM") 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 return d

View File

@ -140,12 +140,15 @@ def process_field(field, cdict, style, suppress_empty_pars=False, format="pdf"):
text = (field or "") % scu.WrapDict( text = (field or "") % scu.WrapDict(
cdict cdict
) # note that None values are mapped to empty strings ) # note that None values are mapped to empty strings
except: except: # pylint: disable=bare-except
log( log(
f"""process_field: invalid format. field={field!r} f"""process_field: invalid format. field={field!r}
values={pprint.pformat(cdict)} 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 = ( text = (
"<para><i>format invalide !</i></para><para>" "<para><i>format invalide !</i></para><para>"
+ traceback.format_exc() + traceback.format_exc()

View File

@ -55,6 +55,7 @@ from reportlab.lib import styles
from flask import g from flask import g
from app.scodoc import sco_utils as scu
from app.scodoc.sco_utils import CONFIG from app.scodoc.sco_utils import CONFIG
from app import log from app import log
from app.scodoc.sco_exceptions import ScoGenError, ScoValueError 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 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" "convert s from string to string suitable for ReportLab"
if not s: if not s:
return "" return ""
@ -145,9 +146,9 @@ def makeParas(txt, style, suppress_empty=False):
) from e ) from e
else: else:
raise e raise e
except Exception as e: except Exception as exc:
log(traceback.format_exc()) log(traceback.format_exc())
log("Invalid pdf para format: %s" % txt) log(f"Invalid pdf para format: {txt}")
try: try:
result = [ result = [
Paragraph( Paragraph(
@ -155,13 +156,14 @@ def makeParas(txt, style, suppress_empty=False):
style, style,
) )
] ]
except ValueError as e: # probleme font ? essaye sans style except ValueError as exc2: # probleme font ? essaye sans style
# recupere font en cause ? # recupere font en cause ?
m = re.match(r".*family/bold/italic for (.*)", e.args[0], re.DOTALL) m = re.match(r".*family/bold/italic for (.*)", e.args[0], re.DOTALL)
if m: if m:
message = f"police non disponible: {m[1]}" message = f"police non disponible: {m[1]}"
else: else:
message = "format invalide" message = "format invalide"
scu.flash_once(f"problème génération PDF: {message}")
return [ return [
Paragraph( Paragraph(
SU(f'<font color="red"><b>Erreur: {message}</b></font>'), SU(f'<font color="red"><b>Erreur: {message}</b></font>'),

View File

@ -57,7 +57,13 @@ from flask import g, request
from app.comp import res_sem from app.comp import res_sem
from app.comp.res_compat import NotesTableCompat 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.sco_utils as scu
import app.scodoc.notesdb as ndb import app.scodoc.notesdb as ndb
@ -81,8 +87,8 @@ from app.scodoc.sco_pdf import PDFLOCK
from app.scodoc.TrivialFormulator import TrivialFormulator from app.scodoc.TrivialFormulator import TrivialFormulator
def _descr_decisions_ues(nt, etudid, decisions_ue, decision_sem): def _descr_decisions_ues(nt, etudid, decisions_ue, decision_sem) -> list[dict]:
"""Liste des UE validées dans ce semestre""" """Liste des UE validées dans ce semestre (incluant les UE capitalisées)"""
if not decisions_ue: if not decisions_ue:
return [] return []
uelist = [] uelist = []
@ -93,14 +99,17 @@ def _descr_decisions_ues(nt, etudid, decisions_ue, decision_sem):
decisions_ue[ue_id]["code"] == sco_codes_parcours.ADM decisions_ue[ue_id]["code"] == sco_codes_parcours.ADM
or ( or (
# XXX ceci devrait dépendre du parcours et non pas être une option ! #sco8 # 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"]) and sco_codes_parcours.code_semestre_validant(decision_sem["code"])
) )
): ):
ue = sco_edit_ue.ue_list(args={"ue_id": ue_id})[0] ue = sco_edit_ue.ue_list(args={"ue_id": ue_id})[0]
uelist.append(ue) uelist.append(ue)
except: 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: # Les UE capitalisées dans d'autres semestres:
if etudid in nt.validations.ue_capitalisees.index: if etudid in nt.validations.ue_capitalisees.index:
for ue_id in nt.validations.ue_capitalisees.loc[[etudid]]["ue_id"]: for ue_id in nt.validations.ue_capitalisees.loc[[etudid]]["ue_id"]:
@ -230,8 +239,11 @@ def dict_pvjury(
L = [] L = []
D = {} # même chose que L, mais { etudid : dec } D = {} # même chose que L, mais { etudid : dec }
for etudid in etudids: for etudid in etudids:
etud = sco_etud.get_etud_info(etudid=etudid, filled=True)[0] # etud = sco_etud.get_etud_info(etudid=etudid, filled=True)[0]
Se = sco_cursus.get_situation_etud_cursus(etud, formsemestre_id) 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 semestre_non_terminal = semestre_non_terminal or Se.semestre_non_terminal
d = {} d = {}
d["identite"] = nt.identdict[etudid] d["identite"] = nt.identdict[etudid]
@ -240,6 +252,8 @@ def dict_pvjury(
) # I|D|DEF (inscription ou démission ou défaillant) ) # I|D|DEF (inscription ou démission ou défaillant)
d["decision_sem"] = nt.get_etud_decision_sem(etudid) d["decision_sem"] = nt.get_etud_decision_sem(etudid)
d["decisions_ue"] = nt.get_etud_decision_ues(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()[ d["last_formsemestre_id"] = Se.get_semestres()[
-1 -1
] # id du dernier semestre (chronologiquement) dans lequel il a été inscrit ] # id du dernier semestre (chronologiquement) dans lequel il a été inscrit
@ -305,12 +319,7 @@ def dict_pvjury(
d["decision_sem"]["compense_formsemestre_id"] d["decision_sem"]["compense_formsemestre_id"]
) )
obs.append( obs.append(
"%s compense %s (%s)" f"""{sem["sem_id_txt"]} compense {compensed["sem_id_txt"]} ({compensed["anneescolaire"]})"""
% (
sem["sem_id_txt"],
compensed["sem_id_txt"],
compensed["anneescolaire"],
)
) )
d["observation"] = ", ".join(obs) d["observation"] = ", ".join(obs)

View File

@ -664,6 +664,15 @@ def flash_errors(form):
# see https://getbootstrap.com/docs/4.0/components/alerts/ # 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 def sendCSVFile(data, filename): # DEPRECATED utiliser send_file
"""publication fichier CSV.""" """publication fichier CSV."""
return send_file(data, filename=filename, mime=CSV_MIMETYPE, attached=True) return send_file(data, filename=filename, mime=CSV_MIMETYPE, attached=True)

View File

@ -2552,9 +2552,8 @@ def formsemestre_validation_suppress_etud(
) )
if not dialog_confirmed: if not dialog_confirmed:
d = sco_bulletins_json.dict_decision_jury( 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", [])] descr_ues = [f"{u['acronyme']}: {u['code']}" for u in d.get("decision_ue", [])]
dec_annee = d.get("decision_annee") dec_annee = d.get("decision_annee")

View File

@ -1,7 +1,7 @@
# -*- mode: python -*- # -*- mode: python -*-
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
SCOVERSION = "9.3.16" SCOVERSION = "9.3.17"
SCONAME = "ScoDoc" SCONAME = "ScoDoc"