PV: lettres individuelles: affichage des UEs et autres réparations.

This commit is contained in:
Emmanuel Viennet 2023-02-19 23:06:44 +01:00
parent 9bf505ff11
commit 7e56dc730d
12 changed files with 67 additions and 65 deletions

View File

@ -385,7 +385,7 @@ class BulletinBUT:
"injustifie": nbabs - nbabsjust, "injustifie": nbabs - nbabsjust,
"total": nbabs, "total": nbabs,
} }
decisions_ues = self.res.get_etud_decision_ues(etud.id) or {} decisions_ues = self.res.get_etud_decisions_ue(etud.id) or {}
if self.prefs["bul_show_ects"]: if self.prefs["bul_show_ects"]:
ects_tot = res.etud_ects_tot_sem(etud.id) ects_tot = res.etud_ects_tot_sem(etud.id)
ects_acquis = res.get_etud_ects_valides(etud.id, decisions_ues) ects_acquis = res.get_etud_ects_valides(etud.id, decisions_ues)

View File

@ -271,9 +271,9 @@ class NotesTableCompat(ResultatsSemestre):
def etud_has_decision(self, etudid): def etud_has_decision(self, etudid):
"""True s'il y a une décision de jury pour cet étudiant""" """True s'il y a une décision de jury pour cet étudiant"""
return self.get_etud_decision_ues(etudid) or self.get_etud_decision_sem(etudid) return self.get_etud_decisions_ue(etudid) or self.get_etud_decision_sem(etudid)
def get_etud_decision_ues(self, etudid: int) -> dict: def get_etud_decisions_ue(self, etudid: int) -> dict:
"""Decisions du jury pour les UE de cet etudiant, ou None s'il n'y en pas eu. """Decisions du jury pour les UE de cet etudiant, ou None s'il n'y en pas eu.
Ne tient pas compte des UE capitalisées. Ne tient pas compte des UE capitalisées.
{ ue_id : { 'code' : ADM|CMP|AJ, 'event_date' : "d/m/y", 'ects' : x } { ue_id : { 'code' : ADM|CMP|AJ, 'event_date' : "d/m/y", 'ects' : x }
@ -288,10 +288,10 @@ class NotesTableCompat(ResultatsSemestre):
def get_etud_ects_valides(self, etudid: int, decisions_ues: dict = False) -> 0: def get_etud_ects_valides(self, etudid: int, decisions_ues: dict = False) -> 0:
"""Le total des ECTS validés (et enregistrés) par l'étudiant dans ce semestre. """Le total des ECTS validés (et enregistrés) par l'étudiant dans ce semestre.
NB: avant jury, rien d'enregistré, donc zéro ECTS. NB: avant jury, rien d'enregistré, donc zéro ECTS.
Optimisation: si decisions_ues est passé, l'utilise, sinon appelle get_etud_decision_ues() Optimisation: si decisions_ues est passé, l'utilise, sinon appelle get_etud_decisions_ue()
""" """
if decisions_ues is False: if decisions_ues is False:
decisions_ues = self.get_etud_decision_ues(etudid) decisions_ues = self.get_etud_decisions_ue(etudid)
if not decisions_ues: if not decisions_ues:
return 0.0 return 0.0
return sum([d.get("ects", 0.0) for d in decisions_ues.values()]) return sum([d.get("ects", 0.0) for d in decisions_ues.values()])

View File

@ -183,6 +183,10 @@ class Identite(db.Model):
e["etudid"] = self.id e["etudid"] = self.id
e["date_naissance"] = ndb.DateISOtoDMY(e["date_naissance"]) e["date_naissance"] = ndb.DateISOtoDMY(e["date_naissance"])
e["ne"] = self.e e["ne"] = self.e
e["nomprenom"] = self.nomprenom
adresse = self.adresses.first()
if adresse:
e.update(adresse.to_dict())
return {k: e[k] or "" for k in e} # convert_null_outputs_to_empty return {k: e[k] or "" for k in e} # convert_null_outputs_to_empty
def to_dict_bul(self, include_urls=True): def to_dict_bul(self, include_urls=True):

View File

@ -1102,7 +1102,7 @@ class NotesTable:
else: else:
return self.decisions_jury.get(etudid, None) return self.decisions_jury.get(etudid, None)
def get_etud_decision_ues(self, etudid): def get_etud_decisions_ue(self, etudid):
"""Decisions du jury pour les UE de cet etudiant, ou None s'il n'y en pas eu. """Decisions du jury pour les UE de cet etudiant, ou None s'il n'y en pas eu.
Ne tient pas compte des UE capitalisées. Ne tient pas compte des UE capitalisées.
{ ue_id : { 'code' : ADM|CMP|AJ, 'event_date' : } { ue_id : { 'code' : ADM|CMP|AJ, 'event_date' : }
@ -1122,7 +1122,7 @@ class NotesTable:
def etud_has_decision(self, etudid): def etud_has_decision(self, etudid):
"""True s'il y a une décision de jury pour cet étudiant""" """True s'il y a une décision de jury pour cet étudiant"""
return self.get_etud_decision_ues(etudid) or self.get_etud_decision_sem(etudid) return self.get_etud_decisions_ue(etudid) or self.get_etud_decision_sem(etudid)
def all_etuds_have_sem_decisions(self): def all_etuds_have_sem_decisions(self):
"""True si tous les étudiants du semestre ont une décision de jury. """True si tous les étudiants du semestre ont une décision de jury.

View File

@ -433,7 +433,7 @@ class ApoEtud(dict):
return VOID_APO_RES return VOID_APO_RES
# Elements UE # Elements UE
decisions_ue = nt.get_etud_decision_ues(etudid) decisions_ue = nt.get_etud_decisions_ue(etudid)
for ue in nt.get_ues_stat_dict(): for ue in nt.get_ues_stat_dict():
if ue["code_apogee"] and code in { if ue["code_apogee"] and code in {
x.strip() for x in ue["code_apogee"].split(",") x.strip() for x in ue["code_apogee"].split(",")

View File

@ -124,6 +124,24 @@ def replacement_function(match):
) )
class WrapDict(object):
"""Wrap a dict so that getitem returns '' when values are None"""
def __init__(self, adict, NoneValue=""):
self.dict = adict
self.NoneValue = NoneValue
def __getitem__(self, key):
try:
value = self.dict[key]
except KeyError:
return f"XXX {key} invalide XXX"
if value is None:
return self.NoneValue
else:
return value
def process_field(field, cdict, style, suppress_empty_pars=False, format="pdf"): def process_field(field, cdict, style, suppress_empty_pars=False, format="pdf"):
"""Process a field given in preferences, returns """Process a field given in preferences, returns
- if format = 'pdf': a list of Platypus objects - if format = 'pdf': a list of Platypus objects
@ -137,18 +155,19 @@ def process_field(field, cdict, style, suppress_empty_pars=False, format="pdf"):
If format = 'html', replaces <para> by <p>. HTML does not allow logos. If format = 'html', replaces <para> by <p>. HTML does not allow logos.
""" """
try: try:
text = (field or "") % scu.WrapDict( # None values are mapped to empty strings by WrapDict
cdict text = (field or "") % WrapDict(cdict)
) # note that None values are mapped to empty strings
except KeyError as exc: except KeyError as exc:
missing_key = exc.args[0] if len(exc.args) > 0 else "?"
log( log(
f"""process_field: KeyError on field={field!r} f"""process_field: KeyError {missing_key} on field={field!r}
values={pprint.pformat(cdict)} values={pprint.pformat(cdict)}
""" """
) )
if len(exc.args) > 0: text = f"""<para><i>format invalide: champs</i> {missing_key} <i>inexistant !</i></para>"""
missing_field = exc.args[0] scu.flash_once(
text = f"""<para><i>format invalide: champs</i> {missing_field} <i>inexistant !</i></para>""" f"Attention: format PDF invalide (champs {field}, clef {missing_key})"
)
except: # pylint: disable=bare-except except: # pylint: disable=bare-except
log( log(
f"""process_field: invalid format. field={field!r} f"""process_field: invalid format. field={field!r}
@ -156,7 +175,7 @@ def process_field(field, cdict, style, suppress_empty_pars=False, format="pdf"):
""" """
) )
# ne sera pas visible si lien vers pdf: # ne sera pas visible si lien vers pdf:
scu.flash_once(f"Attention: format PDF invalide (champs {field}") scu.flash_once(f"Attention: format PDF invalide (champs {field})")
text = ( text = (
"<para><i>format invalide !</i></para><para>" "<para><i>format invalide !</i></para><para>"
+ traceback.format_exc() + traceback.format_exc()

View File

@ -42,6 +42,7 @@ from app.models import (
but_validations, but_validations,
) )
from app.scodoc import codes_cursus from app.scodoc import codes_cursus
from app.scodoc import sco_edit_ue
from app.scodoc import sco_etud from app.scodoc import sco_etud
from app.scodoc import sco_formsemestre from app.scodoc import sco_formsemestre
from app.scodoc import sco_cursus from app.scodoc import sco_cursus
@ -109,7 +110,7 @@ def dict_pvjury(
etudid etudid
) # 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_decisions_ue(etudid)
if formsemestre.formation.is_apc(): if formsemestre.formation.is_apc():
d.update(but_validations.dict_decision_jury(etud, formsemestre)) d.update(but_validations.dict_decision_jury(etud, formsemestre))
d["last_formsemestre_id"] = Se.get_semestres()[ d["last_formsemestre_id"] = Se.get_semestres()[
@ -241,17 +242,17 @@ def _comp_ects_capitalises_by_ue_code(nt: NotesTableCompat, etudid: int):
return ects_by_ue_code return ects_by_ue_code
def _comp_ects_by_ue_code(nt, decision_ues): def _comp_ects_by_ue_code(nt, decisions_ue):
"""Calcul somme des ECTS validés dans ce semestre (sans les UE capitalisées) """Calcul somme des ECTS validés dans ce semestre (sans les UE capitalisées)
decision_ues est le resultat de nt.get_etud_decision_ues decisions_ue est le resultat de nt.get_etud_decisions_ue
Chaque resultat est un dict: { ue_code : ects } Chaque resultat est un dict: { ue_code : ects }
""" """
if not decision_ues: if not decisions_ue:
return {} return {}
ects_by_ue_code = {} ects_by_ue_code = {}
for ue_id in decision_ues: for ue_id in decisions_ue:
d = decision_ues[ue_id] d = decisions_ue[ue_id]
ue = UniteEns.query.get(ue_id) ue = UniteEns.query.get(ue_id)
ects_by_ue_code[ue.ue_code] = d["ects"] ects_by_ue_code[ue.ue_code] = d["ects"]
@ -269,26 +270,22 @@ def _descr_decisions_ues(nt, etudid, decisions_ue, decision_sem) -> list[dict]:
return [] return []
uelist = [] uelist = []
# Les UE validées dans ce semestre: # Les UE validées dans ce semestre:
for ue_id in decisions_ue.keys(): for ue_id in decisions_ue:
try: if decisions_ue[ue_id] and (
if decisions_ue[ue_id] and ( codes_cursus.code_ue_validant(decisions_ue[ue_id].get("code"))
codes_cursus.code_ue_validant(decisions_ue[ue_id]["code"]) or (
or ( (not nt.is_apc)
(not nt.is_apc) and (
and ( # 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 decision_sem
decision_sem and scu.CONFIG.CAPITALIZE_ALL_UES
and scu.CONFIG.CAPITALIZE_ALL_UES and decision_sem
and codes_cursus.code_semestre_validant(decision_sem["code"]) and codes_cursus.code_semestre_validant(decision_sem.get("code"))
)
) )
):
ue = sco_edit_ue.ue_list(args={"ue_id": ue_id})[0]
uelist.append(ue)
except:
log(
f"Exception in descr_decisions_ues: ue_id={ue_id} decisions_ue={decisions_ue}"
) )
):
ue = sco_edit_ue.ue_list(args={"ue_id": ue_id})[0]
uelist.append(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"]:

View File

@ -47,7 +47,6 @@ from app.models import FormSemestre, Identite
import app.scodoc.sco_utils as scu import app.scodoc.sco_utils as scu
from app.scodoc import sco_bulletins_pdf from app.scodoc import sco_bulletins_pdf
from app.scodoc import sco_pv_dict from app.scodoc import sco_pv_dict
from app.scodoc import sco_etud
from app.scodoc import sco_pdf from app.scodoc import sco_pdf
from app.scodoc import sco_preferences from app.scodoc import sco_preferences
from app.scodoc.sco_exceptions import ScoValueError from app.scodoc.sco_exceptions import ScoValueError
@ -70,9 +69,6 @@ def pdf_lettres_individuelles(
dpv = sco_pv_dict.dict_pvjury(formsemestre_id, etudids=etudids, with_prev=True) dpv = sco_pv_dict.dict_pvjury(formsemestre_id, etudids=etudids, with_prev=True)
if not dpv: if not dpv:
return "" return ""
# Ajoute infos sur etudiants
etuds = [x["identite"] for x in dpv["decisions"]]
sco_etud.fill_etuds_info(etuds)
# #
formsemestre: FormSemestre = FormSemestre.query.get(formsemestre_id) formsemestre: FormSemestre = FormSemestre.query.get(formsemestre_id)
prefs = sco_preferences.SemPreferences(formsemestre_id) prefs = sco_preferences.SemPreferences(formsemestre_id)
@ -95,9 +91,10 @@ def pdf_lettres_individuelles(
decision["decision_sem"] decision["decision_sem"]
or decision.get("decision_annee") or decision.get("decision_annee")
or decision.get("decision_rcue") or decision.get("decision_rcue")
or decision.get("decisions_ue")
): # decision prise ): # decision prise
etud: Identite = Identite.query.get(decision["identite"]["etudid"]) etud: Identite = Identite.query.get(decision["identite"]["etudid"])
params["nomEtud"] = etud.nomprenom params["nomEtud"] = etud.nomprenom # backward compat
bookmarks[npages + 1] = scu.suppress_accents(etud.nomprenom) bookmarks[npages + 1] = scu.suppress_accents(etud.nomprenom)
try: try:
objects += pdf_lettre_individuelle( objects += pdf_lettre_individuelle(
@ -217,7 +214,7 @@ def pdf_lettre_individuelle(sem, decision, etud: Identite, params, signature=Non
params.update(decision["identite"]) params.update(decision["identite"])
# fix domicile # fix domicile
if params["domicile"]: if params.get("domicile"):
params["domicile"] = params["domicile"].replace("\\n", "<br/>") params["domicile"] = params["domicile"].replace("\\n", "<br/>")
# UE capitalisées: # UE capitalisées:

View File

@ -953,7 +953,7 @@ def has_existing_decision(M, E, etudid):
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre) nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
if nt.get_etud_decision_sem(etudid): if nt.get_etud_decision_sem(etudid):
return True return True
dec_ues = nt.get_etud_decision_ues(etudid) dec_ues = nt.get_etud_decisions_ue(etudid)
if dec_ues: if dec_ues:
mod = sco_edit_module.module_list({"module_id": M["module_id"]})[0] mod = sco_edit_module.module_list({"module_id": M["module_id"]})[0]
ue_id = mod["ue_id"] ue_id = mod["ue_id"]

View File

@ -291,21 +291,6 @@ class DictDefault(dict): # obsolete, use collections.defaultdict
return value return value
class WrapDict(object):
"""Wrap a dict so that getitem returns '' when values are None"""
def __init__(self, adict, NoneValue=""):
self.dict = adict
self.NoneValue = NoneValue
def __getitem__(self, key):
value = self.dict[key]
if value is None:
return self.NoneValue
else:
return value
def group_by_key(d, key): def group_by_key(d, key):
gr = DictDefault(defaultvalue=[]) gr = DictDefault(defaultvalue=[])
for e in d: for e in d:

View File

@ -158,7 +158,7 @@ class TableJury(TableRecap):
titre, titre,
validation_rcue.code, validation_rcue.code,
group="cursus_" + annee, group="cursus_" + annee,
classes=["recorded_code"], classes=[],
column_classes=["cursus_but" + (" first" if first else "")], column_classes=["cursus_but" + (" first" if first else "")],
target_attrs={ target_attrs={
"title": f"{niveau.competence.titre} niveau {niveau.ordre}" "title": f"{niveau.competence.titre} niveau {niveau.ordre}"
@ -221,7 +221,7 @@ class RowJury(RowRecap):
"Ajoute 2 colonnes: moyenne d'UE et code jury" "Ajoute 2 colonnes: moyenne d'UE et code jury"
# table recap standard (mais avec group différent) # table recap standard (mais avec group différent)
super().add_ue_cols(ue, ue_status, col_group=col_group or "col_ue") super().add_ue_cols(ue, ue_status, col_group=col_group or "col_ue")
dues = self.table.res.get_etud_decision_ues(self.etud.id) dues = self.table.res.get_etud_decisions_ue(self.etud.id)
due = dues.get(ue.id) if dues else None due = dues.get(ue.id) if dues else None
col_id = f"moy_ue_{ue.id}_code" col_id = f"moy_ue_{ue.id}_code"

View File

@ -233,7 +233,7 @@ def run_sco_basic(verbose=False) -> FormSemestre:
formsemestre: FormSemestre = FormSemestre.query.get_or_404(formsemestre_id) formsemestre: FormSemestre = FormSemestre.query.get_or_404(formsemestre_id)
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre) nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
for etud in etuds[:5]: for etud in etuds[:5]:
dec_ues = nt.get_etud_decision_ues(etud["etudid"]) dec_ues = nt.get_etud_decisions_ue(etud["etudid"])
for ue_id in dec_ues: for ue_id in dec_ues:
assert dec_ues[ue_id]["code"] in {"ADM", "CMP"} assert dec_ues[ue_id]["code"] in {"ADM", "CMP"}