PV Jury PDF: refactoring, optimisation, amélioration.

This commit is contained in:
Emmanuel Viennet 2023-02-19 02:54:29 +01:00
parent 52f98b66c1
commit 9e52566781
12 changed files with 469 additions and 421 deletions

View File

@ -1154,7 +1154,7 @@ class DecisionsProposeesRCUE(DecisionsProposees):
def descr_validation(self) -> str: def descr_validation(self) -> str:
"""Description validation niveau enregistrée, pour PV jury. """Description validation niveau enregistrée, pour PV jury.
Si le niveau est validé, done son acronyme, sinon chaine vide. Si le niveau est validé, donne son acronyme, sinon chaine vide.
""" """
if self.code_valide in sco_codes.CODES_RCUE_VALIDES: if self.code_valide in sco_codes.CODES_RCUE_VALIDES:
if ( if (
@ -1164,7 +1164,7 @@ class DecisionsProposeesRCUE(DecisionsProposees):
ordre = self.rcue.ue_1.niveau_competence.ordre ordre = self.rcue.ue_1.niveau_competence.ordre
else: else:
return "?" # oups ? return "?" # oups ?
return f"{niveau_titre} niv. {ordre}" return f"{niveau_titre}-{ordre}"
return "" return ""

View File

@ -44,8 +44,7 @@ def pvjury_page_but(formsemestre_id: int, fmt="html"):
""" """
formsemestre: FormSemestre = FormSemestre.query.get_or_404(formsemestre_id) formsemestre: FormSemestre = FormSemestre.query.get_or_404(formsemestre_id)
assert formsemestre.formation.is_apc() assert formsemestre.formation.is_apc()
title = "Procès-verbal de jury BUT annuel" title = "Procès-verbal de jury BUT"
if fmt == "html": if fmt == "html":
line_sep = "<br>" line_sep = "<br>"
else: else:
@ -93,9 +92,11 @@ def pvjury_page_but(formsemestre_id: int, fmt="html"):
def pvjury_table_but( def pvjury_table_but(
formsemestre: FormSemestre, line_sep: str = "\n" formsemestre: FormSemestre, etudids: list[int] = None, line_sep: str = "\n"
) -> tuple[list[dict], dict]: ) -> tuple[list[dict], dict]:
"table avec résultats jury BUT pour PV" """Table avec résultats jury BUT pour PV.
Si etudids est None, prend tous les étudiants inscrits.
"""
# remplace pour le BUT la fonction sco_pvjury.pvjury_table # remplace pour le BUT la fonction sco_pvjury.pvjury_table
annee_but = (formsemestre.semestre_id + 1) // 2 annee_but = (formsemestre.semestre_id + 1) // 2
titles = { titles = {
@ -110,7 +111,12 @@ def pvjury_table_but(
"observations": "Observations", "observations": "Observations",
} }
rows = [] rows = []
for etudid in formsemestre.etuds_inscriptions: formsemestre_etudids = formsemestre.etuds_inscriptions.keys()
if etudids is None:
etudids = formsemestre_etudids
for etudid in etudids:
if not etudid in formsemestre_etudids:
continue # garde fou
etud: Identite = Identite.query.get(etudid) etud: Identite = Identite.query.get(etudid)
try: try:
deca = jury_but.DecisionsProposeesAnnee(etud, formsemestre) deca = jury_but.DecisionsProposeesAnnee(etud, formsemestre)

View File

@ -12,7 +12,7 @@ import numpy as np
from app.but import jury_but from app.but import jury_but
from app.models.etudiants import Identite from app.models.etudiants import Identite
from app.models.formsemestre import FormSemestre from app.models.formsemestre import FormSemestre
from app.scodoc import sco_pvjury from app.scodoc import sco_dict_pv_jury
def get_jury_but_results(formsemestre: FormSemestre) -> list[dict]: def get_jury_but_results(formsemestre: FormSemestre) -> list[dict]:
@ -20,7 +20,7 @@ def get_jury_but_results(formsemestre: FormSemestre) -> list[dict]:
if formsemestre.formation.referentiel_competence is None: if formsemestre.formation.referentiel_competence is None:
# pas de ref. comp., donc pas de decisions de jury (ne lance pas d'exception) # pas de ref. comp., donc pas de decisions de jury (ne lance pas d'exception)
return [] return []
dpv = sco_pvjury.dict_pvjury(formsemestre.id) dpv = sco_dict_pv_jury.dict_pvjury(formsemestre.id)
rows = [] rows = []
for etudid in formsemestre.etuds_inscriptions: for etudid in formsemestre.etuds_inscriptions:
rows.append(_get_jury_but_etud_result(formsemestre, dpv, etudid)) rows.append(_get_jury_but_etud_result(formsemestre, dpv, etudid))

View File

@ -435,6 +435,12 @@ class FormSemestre(db.Model):
) )
) )
def est_terminal(self) -> bool:
"Vrai si dernier semestre de son cursus (ou formation mono-semestre)"
return (self.semestre_id < 0) or (
self.semestre_id == self.formation.get_cursus().NB_SEM
)
@classmethod @classmethod
def comp_periode( def comp_periode(
cls, cls,

View File

@ -71,14 +71,14 @@ from app.comp import res_sem
from app.comp.res_compat import NotesTableCompat from app.comp.res_compat import NotesTableCompat
from app.models import Departement, FormSemestre from app.models import Departement, FormSemestre
from app.scodoc.TrivialFormulator import TrivialFormulator from app.scodoc.TrivialFormulator import TrivialFormulator
from app.scodoc.sco_exceptions import AccessDenied, ScoPermissionDenied from app.scodoc.sco_exceptions import ScoPermissionDenied
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_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_pvjury from app.scodoc import sco_pvjury
from app.scodoc import sco_dict_pv_jury
from app.scodoc import sco_pvpdf from app.scodoc import sco_pvpdf
from app.scodoc.sco_exceptions import ScoValueError from app.scodoc.sco_exceptions import ScoValueError
@ -299,13 +299,13 @@ def do_formsemestre_archive(
date_jury="", date_jury="",
signature=None, # pour lettres indiv signature=None, # pour lettres indiv
date_commission=None, date_commission=None,
numeroArrete=None, numero_arrete=None,
VDICode=None, code_vdi=None,
showTitle=False, show_title=False,
pv_title=None, pv_title=None,
with_paragraph_nom=False, with_paragraph_nom=False,
anonymous=False, anonymous=False,
bulVersion="long", bul_version="long",
): ):
"""Make and store new archive for this formsemestre. """Make and store new archive for this formsemestre.
Store: Store:
@ -377,7 +377,7 @@ def do_formsemestre_archive(
) )
# Classeur bulletins (PDF) # Classeur bulletins (PDF)
data, _ = sco_bulletins_pdf.get_formsemestre_bulletins_pdf( data, _ = sco_bulletins_pdf.get_formsemestre_bulletins_pdf(
formsemestre_id, version=bulVersion formsemestre_id, version=bul_version
) )
if data: if data:
PVArchive.store(archive_id, "Bulletins.pdf", data) PVArchive.store(archive_id, "Bulletins.pdf", data)
@ -395,14 +395,16 @@ def do_formsemestre_archive(
# PV de jury (PDF): disponible seulement en classique # PV de jury (PDF): disponible seulement en classique
# en BUT, le PV est sous forme excel (Decisions_Jury.xlsx ci-dessus) # en BUT, le PV est sous forme excel (Decisions_Jury.xlsx ci-dessus)
if not formsemestre.formation.is_apc(): if not formsemestre.formation.is_apc():
dpv = sco_pvjury.dict_pvjury(formsemestre_id, etudids=etudids, with_prev=True) dpv = sco_dict_pv_jury.dict_pvjury(
formsemestre_id, etudids=etudids, with_prev=True
)
data = sco_pvpdf.pvjury_pdf( data = sco_pvpdf.pvjury_pdf(
dpv, dpv,
date_commission=date_commission, date_commission=date_commission,
date_jury=date_jury, date_jury=date_jury,
numeroArrete=numeroArrete, numero_arrete=numero_arrete,
VDICode=VDICode, code_vdi=code_vdi,
showTitle=showTitle, show_title=show_title,
pv_title=pv_title, pv_title=pv_title,
with_paragraph_nom=with_paragraph_nom, with_paragraph_nom=with_paragraph_nom,
anonymous=anonymous, anonymous=anonymous,
@ -411,7 +413,7 @@ def do_formsemestre_archive(
PVArchive.store(archive_id, "PV_Jury%s.pdf" % groups_filename, data) PVArchive.store(archive_id, "PV_Jury%s.pdf" % groups_filename, data)
def formsemestre_archive(formsemestre_id, group_ids=[]): def formsemestre_archive(formsemestre_id, group_ids: list[int] = None):
"""Make and store new archive for this formsemestre. """Make and store new archive for this formsemestre.
(all students or only selected groups) (all students or only selected groups)
""" """
@ -424,8 +426,6 @@ def formsemestre_archive(formsemestre_id, group_ids=[]):
formsemestre_id=formsemestre_id, formsemestre_id=formsemestre_id,
) )
) )
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
if not group_ids: if not group_ids:
# tous les inscrits du semestre # tous les inscrits du semestre
group_ids = [sco_groups.get_default_group(formsemestre_id)] group_ids = [sco_groups.get_default_group(formsemestre_id)]
@ -462,7 +462,7 @@ enregistrés et non modifiables, on peut les retrouver ultérieurement.
), ),
("sep", {"input_type": "separator", "title": "Informations sur PV de jury"}), ("sep", {"input_type": "separator", "title": "Informations sur PV de jury"}),
] ]
descr += sco_pvjury.descrform_pvjury(sem) descr += sco_pvjury.descrform_pvjury(formsemestre)
descr += [ descr += [
( (
"signature", "signature",
@ -473,7 +473,7 @@ enregistrés et non modifiables, on peut les retrouver ultérieurement.
}, },
), ),
( (
"bulVersion", "bul_version",
{ {
"input_type": "menu", "input_type": "menu",
"title": "Version des bulletins archivés", "title": "Version des bulletins archivés",
@ -523,20 +523,24 @@ enregistrés et non modifiables, on peut les retrouver ultérieurement.
date_jury=tf[2]["date_jury"], date_jury=tf[2]["date_jury"],
date_commission=tf[2]["date_commission"], date_commission=tf[2]["date_commission"],
signature=signature, signature=signature,
numeroArrete=tf[2]["numeroArrete"], numero_arrete=tf[2]["numero_arrete"],
VDICode=tf[2]["VDICode"], code_vdi=tf[2]["code_vdi"],
pv_title=tf[2]["pv_title"], pv_title=tf[2]["pv_title"],
showTitle=tf[2]["showTitle"], show_title=tf[2]["show_title"],
with_paragraph_nom=tf[2]["with_paragraph_nom"], with_paragraph_nom=tf[2]["with_paragraph_nom"],
anonymous=tf[2]["anonymous"], anonymous=tf[2]["anonymous"],
bulVersion=tf[2]["bulVersion"], bul_version=tf[2]["bul_version"],
) )
msg = "Nouvelle%20archive%20créée" msg = "Nouvelle%20archive%20créée"
# submitted or cancelled: # submitted or cancelled:
flash(msg)
return flask.redirect( return flask.redirect(
"formsemestre_list_archives?formsemestre_id=%s&head_message=%s" url_for(
% (formsemestre_id, msg) "notes.formsemestre_list_archives",
scodoc_dept=g.scodoc_dept,
formsemestre_id=formsemestre_id,
)
) )

View File

@ -56,12 +56,10 @@ from app.scodoc import sco_bulletins_xml
from app.scodoc import codes_cursus from app.scodoc import codes_cursus
from app.scodoc import sco_etud from app.scodoc import sco_etud
from app.scodoc import sco_evaluation_db from app.scodoc import sco_evaluation_db
from app.scodoc import sco_formations
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_permissions_check
from app.scodoc import sco_preferences from app.scodoc import sco_preferences
from app.scodoc import sco_pvjury from app.scodoc import sco_dict_pv_jury
from app.scodoc import sco_users from app.scodoc import sco_users
import app.scodoc.sco_utils as scu import app.scodoc.sco_utils as scu
from app.scodoc.sco_utils import ModuleType, fmt_note from app.scodoc.sco_utils import ModuleType, fmt_note
@ -789,7 +787,7 @@ def etud_descr_situation_semestre(
infos["date_defaillance"] = date_def infos["date_defaillance"] = date_def
infos["descr_decision_jury"] = f"Défaillant{ne}" infos["descr_decision_jury"] = f"Défaillant{ne}"
dpv = sco_pvjury.dict_pvjury(formsemestre_id, etudids=[etudid]) dpv = sco_dict_pv_jury.dict_pvjury(formsemestre_id, etudids=[etudid])
if dpv: if dpv:
infos["decision_sem"] = dpv["decisions"][0]["decision_sem"] infos["decision_sem"] = dpv["decisions"][0]["decision_sem"]

View File

@ -0,0 +1,324 @@
# -*- mode: python -*-
# -*- coding: utf-8 -*-
##############################################################################
#
# Gestion scolarite IUT
#
# Copyright (c) 1999 - 2023 Emmanuel Viennet. All rights reserved.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
# Emmanuel Viennet emmanuel.viennet@viennet.net
#
##############################################################################
"""Ancienne fonction de synthèse des information jury
(pour formations classiques)
"""
from operator import itemgetter
from app import log
from app.comp import res_sem
from app.comp.res_compat import NotesTableCompat
from app.models import (
Formation,
FormSemestre,
Identite,
ScolarAutorisationInscription,
UniteEns,
but_validations,
)
from app.scodoc import codes_cursus
from app.scodoc import sco_etud
from app.scodoc import sco_formsemestre
from app.scodoc import sco_cursus
from app.scodoc import sco_cursus_dut
import app.scodoc.notesdb as ndb
import app.scodoc.sco_utils as scu
def dict_pvjury(
formsemestre_id,
etudids=None,
with_prev=False,
with_parcours_decisions=False,
):
"""Données pour édition jury
etudids == None => tous les inscrits, sinon donne la liste des ids
Si with_prev: ajoute infos sur code jury semestre precedent
Si with_parcours_decisions: ajoute infos sur code decision jury de tous les semestre du parcours
Résultat:
{
'date' : date de la decision la plus recente,
'formsemestre' : sem,
'is_apc' : bool,
'formation' : { 'acronyme' :, 'titre': ... }
'decisions' : { [ { 'identite' : {'nom' :, 'prenom':, ...,},
'etat' : I ou D ou DEF
'decision_sem' : {'code':, 'code_prev': },
'decisions_ue' : { ue_id : { 'code' : ADM|CMP|AJ, 'event_date' :,
'acronyme', 'numero': } },
'autorisations' : [ { 'semestre_id' : { ... } } ],
'validation_parcours' : True si parcours validé (diplome obtenu)
'prev_code' : code (calculé slt si with_prev),
'mention' : mention (en fct moy gen),
'sum_ects' : total ECTS acquis dans ce semestre (incluant les UE capitalisées)
'sum_ects_capitalises' : somme des ECTS des UE capitalisees
}
]
},
'decisions_dict' : { etudid : decision (comme ci-dessus) },
}
"""
formsemestre: FormSemestre = FormSemestre.query.get_or_404(formsemestre_id)
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
if etudids is None:
etudids = nt.get_etudids()
if not etudids:
return {}
cnx = ndb.GetDBConnexion()
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
max_date = "0000-01-01"
has_prev = False # vrai si au moins un etudiant a un code prev
semestre_non_terminal = False # True si au moins un etudiant a un devenir
decisions = []
D = {} # même chose que decisions, mais { etudid : dec }
for etudid in etudids:
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]
d["etat"] = nt.get_etud_etat(
etudid
) # 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
ects_capitalises_by_ue_code = _comp_ects_capitalises_by_ue_code(nt, etudid)
d["sum_ects_capitalises"] = sum(ects_capitalises_by_ue_code.values())
ects_by_ue_code = _comp_ects_by_ue_code(nt, d["decisions_ue"])
d["sum_ects"] = _sum_ects_dicts(ects_capitalises_by_ue_code, ects_by_ue_code)
if d["decision_sem"] and codes_cursus.code_semestre_validant(
d["decision_sem"]["code"]
):
d["mention"] = scu.get_mention(nt.get_etud_moy_gen(etudid))
else:
d["mention"] = ""
# Versions "en français": (avec les UE capitalisées d'ailleurs)
dec_ue_list = _descr_decisions_ues(
nt, etudid, d["decisions_ue"], d["decision_sem"]
)
d["decisions_ue_nb"] = len(
dec_ue_list
) # avec les UE capitalisées, donc des éventuels doublons
# Mais sur la description (eg sur les bulletins), on ne veut pas
# afficher ces doublons: on uniquifie sur ue_code
_codes = set()
ue_uniq = []
for ue in dec_ue_list:
if ue["ue_code"] not in _codes:
ue_uniq.append(ue)
_codes.add(ue["ue_code"])
d["decisions_ue_descr"] = ", ".join([ue["acronyme"] for ue in ue_uniq])
if nt.is_apc:
d["decision_sem_descr"] = "" # pas de validation de semestre en BUT
else:
d["decision_sem_descr"] = _descr_decision_sem(d["etat"], d["decision_sem"])
autorisations = ScolarAutorisationInscription.query.filter_by(
etudid=etudid, origin_formsemestre_id=formsemestre_id
).all()
d["autorisations"] = [a.to_dict() for a in autorisations]
d["autorisations_descr"] = _descr_autorisations(autorisations)
d["validation_parcours"] = Se.parcours_validated()
d["parcours"] = Se.get_cursus_descr(filter_futur=True)
if with_parcours_decisions:
d["parcours_decisions"] = Se.get_parcours_decisions()
# Observations sur les compensations:
compensators = sco_cursus_dut.scolar_formsemestre_validation_list(
cnx, args={"compense_formsemestre_id": formsemestre_id, "etudid": etudid}
)
obs = []
for compensator in compensators:
# nb: il ne devrait y en avoir qu'un !
csem = sco_formsemestre.get_formsemestre(compensator["formsemestre_id"])
obs.append(
"%s compensé par %s (%s)"
% (sem["sem_id_txt"], csem["sem_id_txt"], csem["anneescolaire"])
)
if d["decision_sem"] and d["decision_sem"]["compense_formsemestre_id"]:
compensed = sco_formsemestre.get_formsemestre(
d["decision_sem"]["compense_formsemestre_id"]
)
obs.append(
f"""{sem["sem_id_txt"]} compense {compensed["sem_id_txt"]} ({compensed["anneescolaire"]})"""
)
d["observation"] = ", ".join(obs)
# Cherche la date de decision (sem ou UE) la plus récente:
if d["decision_sem"]:
date = ndb.DateDMYtoISO(d["decision_sem"]["event_date"])
if date and date > max_date: # decision plus recente
max_date = date
if d["decisions_ue"]:
for dec_ue in d["decisions_ue"].values():
if dec_ue:
date = ndb.DateDMYtoISO(dec_ue["event_date"])
if date and date > max_date: # decision plus recente
max_date = date
# Code semestre precedent
if with_prev: # optionnel car un peu long...
info = sco_etud.get_etud_info(etudid=etudid, filled=True)
if not info:
continue # should not occur
etud = info[0]
if Se.prev and Se.prev_decision:
d["prev_decision_sem"] = Se.prev_decision
d["prev_code"] = Se.prev_decision["code"]
d["prev_code_descr"] = _descr_decision_sem(
scu.INSCRIT, Se.prev_decision
)
d["prev"] = Se.prev
has_prev = True
else:
d["prev_decision_sem"] = None
d["prev_code"] = ""
d["prev_code_descr"] = ""
d["Se"] = Se
decisions.append(d)
D[etudid] = d
return {
"date": ndb.DateISOtoDMY(max_date),
"formsemestre": sem,
"is_apc": nt.is_apc,
"has_prev": has_prev,
"semestre_non_terminal": semestre_non_terminal,
"formation": Formation.query.get_or_404(sem["formation_id"]).to_dict(),
"decisions": decisions,
"decisions_dict": D,
}
def _comp_ects_capitalises_by_ue_code(nt: NotesTableCompat, etudid: int):
"""Calcul somme des ECTS des UE capitalisees"""
ues = nt.get_ues_stat_dict()
ects_by_ue_code = {}
for ue in ues:
ue_status = nt.get_etud_ue_status(etudid, ue["ue_id"])
if ue_status and ue_status["is_capitalized"]:
ects_val = float(ue_status["ue"]["ects"] or 0.0)
ects_by_ue_code[ue["ue_code"]] = ects_val
return ects_by_ue_code
def _comp_ects_by_ue_code(nt, decision_ues):
"""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
Chaque resultat est un dict: { ue_code : ects }
"""
if not decision_ues:
return {}
ects_by_ue_code = {}
for ue_id in decision_ues:
d = decision_ues[ue_id]
ue = UniteEns.query.get(ue_id)
ects_by_ue_code[ue.ue_code] = d["ects"]
return ects_by_ue_code
def _descr_autorisations(autorisations: list[ScolarAutorisationInscription]) -> str:
"résumé textuel des autorisations d'inscription (-> 'S1, S3' )"
return ", ".join([f"S{a.semestre_id}" for a in autorisations])
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 = []
# Les UE validées dans ce semestre:
for ue_id in decisions_ue.keys():
try:
if decisions_ue[ue_id] and (
codes_cursus.code_ue_validant(decisions_ue[ue_id]["code"])
or (
(not nt.is_apc)
and (
# XXX ceci devrait dépendre du parcours et non pas être une option ! #sco8
decision_sem
and scu.CONFIG.CAPITALIZE_ALL_UES
and codes_cursus.code_semestre_validant(decision_sem["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}"
)
# 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"]:
try:
uelist.append(nt.get_etud_ue_status(etudid, ue_id)["ue"])
except (KeyError, TypeError):
pass
uelist.sort(key=itemgetter("numero"))
return uelist
def _descr_decision_sem(etat, decision_sem):
"résumé textuel de la décision de semestre"
if etat == "D":
decision = "Démission"
else:
if decision_sem:
cod = decision_sem["code"]
decision = codes_cursus.CODES_EXPL.get(cod, "") # + ' (%s)' % cod
else:
decision = ""
return decision
def _sum_ects_dicts(s, t):
"""Somme deux dictionnaires { ue_code : ects },
quand une UE de même code apparait deux fois, prend celle avec le plus d'ECTS.
"""
sum_ects = sum(s.values()) + sum(t.values())
for ue_code in set(s).intersection(set(t)):
sum_ects -= min(s[ue_code], t[ue_code])
return sum_ects

View File

@ -39,9 +39,8 @@ from app.models import Formation
from app.scodoc import html_sco_header from app.scodoc import html_sco_header
from app.scodoc import sco_bac from app.scodoc import sco_bac
from app.scodoc import codes_cursus from app.scodoc import codes_cursus
from app.scodoc import sco_formations
from app.scodoc import sco_preferences from app.scodoc import sco_preferences
from app.scodoc import sco_pvjury from app.scodoc import sco_dict_pv_jury
from app.scodoc import sco_etud from app.scodoc import sco_etud
import sco_version import sco_version
from app.scodoc.gen_tables import GenTable from app.scodoc.gen_tables import GenTable
@ -58,7 +57,7 @@ def _build_results_table(start_date=None, end_date=None, types_parcours=[]):
# Décisions de jury de tous les semestres: # Décisions de jury de tous les semestres:
dpv_by_sem = {} dpv_by_sem = {}
for formsemestre_id in formsemestre_ids: for formsemestre_id in formsemestre_ids:
dpv_by_sem[formsemestre_id] = sco_pvjury.dict_pvjury( dpv_by_sem[formsemestre_id] = sco_dict_pv_jury.dict_pvjury(
formsemestre_id, with_parcours_decisions=True formsemestre_id, with_parcours_decisions=True
) )
@ -349,7 +348,7 @@ end_date='2017-08-31'
formsemestre_ids = get_set_formsemestre_id_dates( start_date, end_date) formsemestre_ids = get_set_formsemestre_id_dates( start_date, end_date)
dpv_by_sem = {} dpv_by_sem = {}
for formsemestre_id in formsemestre_ids: for formsemestre_id in formsemestre_ids:
dpv_by_sem[formsemestre_id] = sco_pvjury.dict_pvjury( formsemestre_id, with_parcours_decisions=True) dpv_by_sem[formsemestre_id] = sco_dict_pv_jury.dict_pvjury( formsemestre_id, with_parcours_decisions=True)
semlist = [ dpv['formsemestre'] for dpv in dpv_by_sem.values() ] semlist = [ dpv['formsemestre'] for dpv in dpv_by_sem.values() ]

View File

@ -64,7 +64,7 @@ from app.scodoc import sco_cursus_dut
from app.scodoc.sco_cursus_dut import etud_est_inscrit_ue from app.scodoc.sco_cursus_dut import etud_est_inscrit_ue
from app.scodoc import sco_photos from app.scodoc import sco_photos
from app.scodoc import sco_preferences from app.scodoc import sco_preferences
from app.scodoc import sco_pvjury from app.scodoc import sco_dict_pv_jury
# ------------------------------------------------------------------------------------ # ------------------------------------------------------------------------------------
def formsemestre_validation_etud_form( def formsemestre_validation_etud_form(
@ -562,7 +562,7 @@ def formsemestre_recap_parcours_table(
is_cur = Se.formsemestre_id == sem["formsemestre_id"] is_cur = Se.formsemestre_id == sem["formsemestre_id"]
num_sem += 1 num_sem += 1
dpv = sco_pvjury.dict_pvjury(sem["formsemestre_id"], etudids=[etudid]) dpv = sco_dict_pv_jury.dict_pvjury(sem["formsemestre_id"], etudids=[etudid])
pv = dpv["decisions"][0] pv = dpv["decisions"][0]
decision_sem = pv["decision_sem"] decision_sem = pv["decision_sem"]
decisions_ue = pv["decisions_ue"] decisions_ue = pv["decisions_ue"]

View File

@ -47,7 +47,7 @@ from app.scodoc import sco_formsemestre
from app.scodoc import sco_formsemestre_inscriptions from app.scodoc import sco_formsemestre_inscriptions
from app.scodoc import sco_groups from app.scodoc import sco_groups
from app.scodoc import sco_preferences from app.scodoc import sco_preferences
from app.scodoc import sco_pvjury from app.scodoc import sco_dict_pv_jury
from app.scodoc.sco_exceptions import ScoValueError from app.scodoc.sco_exceptions import ScoValueError
@ -137,7 +137,7 @@ def list_inscrits(formsemestre_id, with_dems=False):
def list_etuds_from_sem(src, dst) -> list[dict]: def list_etuds_from_sem(src, dst) -> list[dict]:
"""Liste des etudiants du semestre src qui sont autorisés à passer dans le semestre dst.""" """Liste des etudiants du semestre src qui sont autorisés à passer dans le semestre dst."""
target = dst["semestre_id"] target = dst["semestre_id"]
dpv = sco_pvjury.dict_pvjury(src["formsemestre_id"]) dpv = sco_dict_pv_jury.dict_pvjury(src["formsemestre_id"])
if not dpv: if not dpv:
return [] return []
etuds = [ etuds = [

View File

@ -47,7 +47,6 @@ Jury de semestre n
""" """
import time import time
from operator import itemgetter
from reportlab.platypus import Paragraph from reportlab.platypus import Paragraph
from reportlab.lib import styles from reportlab.lib import styles
@ -55,25 +54,18 @@ import flask
from flask import flash, redirect, url_for from flask import flash, redirect, url_for
from flask import g, request from flask import g, request
from app.comp import res_sem
from app.comp.res_compat import NotesTableCompat
from app.models import ( from app.models import (
Formation, Formation,
FormSemestre, FormSemestre,
UniteEns,
ScolarAutorisationInscription, ScolarAutorisationInscription,
but_validations,
) )
from app.models.etudiants import Identite 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
from app import log
from app.scodoc import html_sco_header from app.scodoc import html_sco_header
from app.scodoc import codes_cursus from app.scodoc import codes_cursus
from app.scodoc import sco_cursus from app.scodoc import sco_dict_pv_jury
from app.scodoc import sco_cursus_dut
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_groups from app.scodoc import sco_groups
@ -87,57 +79,6 @@ 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) -> list[dict]:
"""Liste des UE validées dans ce semestre (incluant les UE capitalisées)"""
if not decisions_ue:
return []
uelist = []
# Les UE validées dans ce semestre:
for ue_id in decisions_ue.keys():
try:
if decisions_ue[ue_id] and (
codes_cursus.code_ue_validant(decisions_ue[ue_id]["code"])
or (
(not nt.is_apc)
and (
# XXX ceci devrait dépendre du parcours et non pas être une option ! #sco8
decision_sem
and scu.CONFIG.CAPITALIZE_ALL_UES
and codes_cursus.code_semestre_validant(decision_sem["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}"
)
# 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"]:
try:
uelist.append(nt.get_etud_ue_status(etudid, ue_id)["ue"])
except (KeyError, TypeError):
pass
uelist.sort(key=itemgetter("numero"))
return uelist
def _descr_decision_sem(etat, decision_sem):
"résumé textuel de la décision de semestre"
if etat == "D":
decision = "Démission"
else:
if decision_sem:
cod = decision_sem["code"]
decision = codes_cursus.CODES_EXPL.get(cod, "") # + ' (%s)' % cod
else:
decision = ""
return decision
def _descr_decision_sem_abbrev(etat, decision_sem): def _descr_decision_sem_abbrev(etat, decision_sem):
"résumé textuel tres court (code) de la décision de semestre" "résumé textuel tres court (code) de la décision de semestre"
if etat == "D": if etat == "D":
@ -150,230 +91,6 @@ def _descr_decision_sem_abbrev(etat, decision_sem):
return decision return decision
def descr_autorisations(autorisations: list[ScolarAutorisationInscription]) -> str:
"résumé textuel des autorisations d'inscription (-> 'S1, S3' )"
return ", ".join([f"S{a.semestre_id}" for a in autorisations])
def _comp_ects_by_ue_code(nt, decision_ues):
"""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
Chaque resultat est un dict: { ue_code : ects }
"""
if not decision_ues:
return {}
ects_by_ue_code = {}
for ue_id in decision_ues:
d = decision_ues[ue_id]
ue = UniteEns.query.get(ue_id)
ects_by_ue_code[ue.ue_code] = d["ects"]
return ects_by_ue_code
def _comp_ects_capitalises_by_ue_code(nt: NotesTableCompat, etudid: int):
"""Calcul somme des ECTS des UE capitalisees"""
ues = nt.get_ues_stat_dict()
ects_by_ue_code = {}
for ue in ues:
ue_status = nt.get_etud_ue_status(etudid, ue["ue_id"])
if ue_status and ue_status["is_capitalized"]:
ects_val = float(ue_status["ue"]["ects"] or 0.0)
ects_by_ue_code[ue["ue_code"]] = ects_val
return ects_by_ue_code
def _sum_ects_dicts(s, t):
"""Somme deux dictionnaires { ue_code : ects },
quand une UE de même code apparait deux fois, prend celle avec le plus d'ECTS.
"""
sum_ects = sum(s.values()) + sum(t.values())
for ue_code in set(s).intersection(set(t)):
sum_ects -= min(s[ue_code], t[ue_code])
return sum_ects
def dict_pvjury(
formsemestre_id,
etudids=None,
with_prev=False,
with_parcours_decisions=False,
):
"""Données pour édition jury
etudids == None => tous les inscrits, sinon donne la liste des ids
Si with_prev: ajoute infos sur code jury semestre precedent
Si with_parcours_decisions: ajoute infos sur code decision jury de tous les semestre du parcours
Résultat:
{
'date' : date de la decision la plus recente,
'formsemestre' : sem,
'is_apc' : bool,
'formation' : { 'acronyme' :, 'titre': ... }
'decisions' : { [ { 'identite' : {'nom' :, 'prenom':, ...,},
'etat' : I ou D ou DEF
'decision_sem' : {'code':, 'code_prev': },
'decisions_ue' : { ue_id : { 'code' : ADM|CMP|AJ, 'event_date' :,
'acronyme', 'numero': } },
'autorisations' : [ { 'semestre_id' : { ... } } ],
'validation_parcours' : True si parcours validé (diplome obtenu)
'prev_code' : code (calculé slt si with_prev),
'mention' : mention (en fct moy gen),
'sum_ects' : total ECTS acquis dans ce semestre (incluant les UE capitalisées)
'sum_ects_capitalises' : somme des ECTS des UE capitalisees
}
]
},
'decisions_dict' : { etudid : decision (comme ci-dessus) },
}
"""
formsemestre: FormSemestre = FormSemestre.query.get_or_404(formsemestre_id)
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
if etudids is None:
etudids = nt.get_etudids()
if not etudids:
return {}
cnx = ndb.GetDBConnexion()
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
max_date = "0000-01-01"
has_prev = False # vrai si au moins un etudiant a un code prev
semestre_non_terminal = False # True si au moins un etudiant a un devenir
decisions = []
D = {} # même chose que decisions, mais { etudid : dec }
for etudid in etudids:
# 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]
d["etat"] = nt.get_etud_etat(
etudid
) # 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
ects_capitalises_by_ue_code = _comp_ects_capitalises_by_ue_code(nt, etudid)
d["sum_ects_capitalises"] = sum(ects_capitalises_by_ue_code.values())
ects_by_ue_code = _comp_ects_by_ue_code(nt, d["decisions_ue"])
d["sum_ects"] = _sum_ects_dicts(ects_capitalises_by_ue_code, ects_by_ue_code)
if d["decision_sem"] and codes_cursus.code_semestre_validant(
d["decision_sem"]["code"]
):
d["mention"] = scu.get_mention(nt.get_etud_moy_gen(etudid))
else:
d["mention"] = ""
# Versions "en français": (avec les UE capitalisées d'ailleurs)
dec_ue_list = _descr_decisions_ues(
nt, etudid, d["decisions_ue"], d["decision_sem"]
)
d["decisions_ue_nb"] = len(
dec_ue_list
) # avec les UE capitalisées, donc des éventuels doublons
# Mais sur la description (eg sur les bulletins), on ne veut pas
# afficher ces doublons: on uniquifie sur ue_code
_codes = set()
ue_uniq = []
for ue in dec_ue_list:
if ue["ue_code"] not in _codes:
ue_uniq.append(ue)
_codes.add(ue["ue_code"])
d["decisions_ue_descr"] = ", ".join([ue["acronyme"] for ue in ue_uniq])
if nt.is_apc:
d["decision_sem_descr"] = "" # pas de validation de semestre en BUT
else:
d["decision_sem_descr"] = _descr_decision_sem(d["etat"], d["decision_sem"])
autorisations = ScolarAutorisationInscription.query.filter_by(
etudid=etudid, origin_formsemestre_id=formsemestre_id
).all()
d["autorisations"] = [a.to_dict() for a in autorisations]
d["autorisations_descr"] = descr_autorisations(autorisations)
d["validation_parcours"] = Se.parcours_validated()
d["parcours"] = Se.get_cursus_descr(filter_futur=True)
if with_parcours_decisions:
d["parcours_decisions"] = Se.get_parcours_decisions()
# Observations sur les compensations:
compensators = sco_cursus_dut.scolar_formsemestre_validation_list(
cnx, args={"compense_formsemestre_id": formsemestre_id, "etudid": etudid}
)
obs = []
for compensator in compensators:
# nb: il ne devrait y en avoir qu'un !
csem = sco_formsemestre.get_formsemestre(compensator["formsemestre_id"])
obs.append(
"%s compensé par %s (%s)"
% (sem["sem_id_txt"], csem["sem_id_txt"], csem["anneescolaire"])
)
if d["decision_sem"] and d["decision_sem"]["compense_formsemestre_id"]:
compensed = sco_formsemestre.get_formsemestre(
d["decision_sem"]["compense_formsemestre_id"]
)
obs.append(
f"""{sem["sem_id_txt"]} compense {compensed["sem_id_txt"]} ({compensed["anneescolaire"]})"""
)
d["observation"] = ", ".join(obs)
# Cherche la date de decision (sem ou UE) la plus récente:
if d["decision_sem"]:
date = ndb.DateDMYtoISO(d["decision_sem"]["event_date"])
if date and date > max_date: # decision plus recente
max_date = date
if d["decisions_ue"]:
for dec_ue in d["decisions_ue"].values():
if dec_ue:
date = ndb.DateDMYtoISO(dec_ue["event_date"])
if date and date > max_date: # decision plus recente
max_date = date
# Code semestre precedent
if with_prev: # optionnel car un peu long...
info = sco_etud.get_etud_info(etudid=etudid, filled=True)
if not info:
continue # should not occur
etud = info[0]
if Se.prev and Se.prev_decision:
d["prev_decision_sem"] = Se.prev_decision
d["prev_code"] = Se.prev_decision["code"]
d["prev_code_descr"] = _descr_decision_sem(
scu.INSCRIT, Se.prev_decision
)
d["prev"] = Se.prev
has_prev = True
else:
d["prev_decision_sem"] = None
d["prev_code"] = ""
d["prev_code_descr"] = ""
d["Se"] = Se
decisions.append(d)
D[etudid] = d
return {
"date": ndb.DateISOtoDMY(max_date),
"formsemestre": sem,
"is_apc": nt.is_apc,
"has_prev": has_prev,
"semestre_non_terminal": semestre_non_terminal,
"formation": Formation.query.get_or_404(sem["formation_id"]).to_dict(),
"decisions": decisions,
"decisions_dict": D,
}
def pvjury_table( def pvjury_table(
dpv, dpv,
only_diplome=False, only_diplome=False,
@ -420,7 +137,7 @@ def pvjury_table(
if dpv["has_prev"]: if dpv["has_prev"]:
id_prev = sem["semestre_id"] - 1 # numero du semestre precedent id_prev = sem["semestre_id"] - 1 # numero du semestre precedent
titles["prev_decision"] = "Décision S%s" % id_prev titles["prev_decision"] = f"Décision S{id_prev}"
columns_ids += ["prev_decision"] columns_ids += ["prev_decision"]
if not dpv["is_apc"]: if not dpv["is_apc"]:
@ -528,7 +245,7 @@ def formsemestre_pvjury(formsemestre_id, format="html", publish=True): # XXX
footer = html_sco_header.sco_footer() footer = html_sco_header.sco_footer()
dpv = dict_pvjury(formsemestre_id, with_prev=True) dpv = sco_dict_pv_jury.dict_pvjury(formsemestre_id, with_prev=True)
if not dpv: if not dpv:
if format == "html": if format == "html":
return ( return (
@ -628,7 +345,7 @@ def formsemestre_pvjury_pdf(formsemestre_id, group_ids: list[int] = None, etudid
Si etudid, PV pour un seul etudiant. Sinon, tout les inscrits au groupe indiqué. Si etudid, PV pour un seul etudiant. Sinon, tout les inscrits au groupe indiqué.
""" """
group_ids = group_ids or [] group_ids = group_ids or []
sem = sco_formsemestre.get_formsemestre(formsemestre_id) formsemestre: FormSemestre = FormSemestre.query.get_or_404(formsemestre_id)
# Mise à jour des groupes d'étapes: # Mise à jour des groupes d'étapes:
sco_groups.create_etapes_partition(formsemestre_id) sco_groups.create_etapes_partition(formsemestre_id)
groups_infos = None groups_infos = None
@ -671,7 +388,7 @@ def formsemestre_pvjury_pdf(formsemestre_id, group_ids: list[int] = None, etudid
</p>""", </p>""",
html_sco_header.sco_footer(), html_sco_header.sco_footer(),
] ]
descr = descrform_pvjury(sem) descr = descrform_pvjury(formsemestre)
if etudid: if etudid:
descr.append(("etudid", {"input_type": "hidden"})) descr.append(("etudid", {"input_type": "hidden"}))
@ -706,43 +423,36 @@ def formsemestre_pvjury_pdf(formsemestre_id, group_ids: list[int] = None, etudid
) )
else: else:
# submit # submit
dpv = dict_pvjury(formsemestre_id, etudids=etudids, with_prev=True) tf[2]["show_title"] = bool(tf[2]["show_title"])
if tf[2]["showTitle"]: tf[2]["anonymous"] = bool(tf[2]["anonymous"])
tf[2]["showTitle"] = True
else:
tf[2]["showTitle"] = False
if tf[2]["anonymous"]:
tf[2]["anonymous"] = True
else:
tf[2]["anonymous"] = False
try: try:
PDFLOCK.acquire() PDFLOCK.acquire()
pdfdoc = sco_pvpdf.pvjury_pdf( pdfdoc = sco_pvpdf.pvjury_pdf(
dpv, formsemestre,
numeroArrete=tf[2]["numeroArrete"], etudids,
VDICode=tf[2]["VDICode"], numero_arrete=tf[2]["numero_arrete"],
code_vdi=tf[2]["code_vdi"],
date_commission=tf[2]["date_commission"], date_commission=tf[2]["date_commission"],
date_jury=tf[2]["date_jury"], date_jury=tf[2]["date_jury"],
showTitle=tf[2]["showTitle"], show_title=tf[2]["show_title"],
pv_title=tf[2]["pv_title"], pv_title=tf[2]["pv_title"],
with_paragraph_nom=tf[2]["with_paragraph_nom"], with_paragraph_nom=tf[2]["with_paragraph_nom"],
anonymous=tf[2]["anonymous"], anonymous=tf[2]["anonymous"],
) )
finally: finally:
PDFLOCK.release() PDFLOCK.release()
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
date_iso = time.strftime("%Y-%m-%d") date_iso = time.strftime("%Y-%m-%d")
if groups_infos: if groups_infos:
groups_filename = "-" + groups_infos.groups_filename groups_filename = "-" + groups_infos.groups_filename
else: else:
groups_filename = "" groups_filename = ""
filename = f"""PV-{sem["titre_num"]}{groups_filename}-{date_iso}.pdf""" filename = f"""PV-{formsemestre.titre_num()}{groups_filename}-{date_iso}.pdf"""
return scu.sendPDFFile(pdfdoc, filename) return scu.sendPDFFile(pdfdoc, filename)
def descrform_pvjury(sem): def descrform_pvjury(formsemestre: FormSemestre):
"""Définition de formulaire pour PV jury PDF""" """Définition de formulaire pour PV jury PDF"""
f_dict = Formation.query.get_or_404(sem["formation_id"]).to_dict() f_dict = formsemestre.formation.to_dict()
return [ return [
( (
"date_commission", "date_commission",
@ -763,7 +473,7 @@ def descrform_pvjury(sem):
}, },
), ),
( (
"numeroArrete", "numero_arrete",
{ {
"input_type": "text", "input_type": "text",
"size": 50, "size": 50,
@ -772,7 +482,7 @@ def descrform_pvjury(sem):
}, },
), ),
( (
"VDICode", "code_vdi",
{ {
"input_type": "text", "input_type": "text",
"size": 15, "size": 15,
@ -791,11 +501,11 @@ def descrform_pvjury(sem):
}, },
), ),
( (
"showTitle", "show_title",
{ {
"input_type": "checkbox", "input_type": "checkbox",
"title": "Indiquer en plus le titre du semestre sur le PV", "title": "Indiquer en plus le titre du semestre sur le PV",
"explanation": '(le titre est "%s")' % sem["titre"], "explanation": f'(le titre est "{formsemestre.titre}")',
"labels": [""], "labels": [""],
"allowed_values": ("1",), "allowed_values": ("1",),
}, },

View File

@ -49,6 +49,7 @@ 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 codes_cursus from app.scodoc import codes_cursus
from app.scodoc import sco_dict_pv_jury
from app.scodoc import sco_etud 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
@ -384,9 +385,7 @@ def pdf_lettres_individuelles(
(tous ceux du semestre, ou la liste indiquée par etudids) (tous ceux du semestre, ou la liste indiquée par etudids)
Renvoie pdf data ou chaine vide si aucun etudiant avec décision de jury. Renvoie pdf data ou chaine vide si aucun etudiant avec décision de jury.
""" """
from app.scodoc import sco_pvjury dpv = sco_dict_pv_jury.dict_pvjury(formsemestre_id, etudids=etudids, with_prev=True)
dpv = sco_pvjury.dict_pvjury(formsemestre_id, etudids=etudids, with_prev=True)
if not dpv: if not dpv:
return "" return ""
# Ajoute infos sur etudiants # Ajoute infos sur etudiants
@ -656,60 +655,57 @@ def add_apc_infos(formsemestre: FormSemestre, params: dict, decision: dict):
# ---------------------------------------------- # ----------------------------------------------
def pvjury_pdf( def pvjury_pdf(
dpv, formsemestre: FormSemestre,
etudids: list[int],
date_commission=None, date_commission=None,
date_jury=None, date_jury=None,
numeroArrete=None, numero_arrete=None,
VDICode=None, code_vdi=None,
showTitle=False, show_title=False,
pv_title=None, pv_title=None,
with_paragraph_nom=False, with_paragraph_nom=False,
anonymous=False, anonymous=False,
): ) -> bytes:
"""Doc PDF récapitulant les décisions de jury """Doc PDF récapitulant les décisions de jury
(tableau en format paysage) (tableau en format paysage)
dpv: result of dict_pvjury
""" """
if not dpv: objects, a_diplome = _pvjury_pdf_type(
return {} formsemestre,
sem = dpv["formsemestre"] etudids,
formsemestre_id = sem["formsemestre_id"]
objects = _pvjury_pdf_type(
dpv,
only_diplome=False, only_diplome=False,
date_commission=date_commission, date_commission=date_commission,
numero_arrete=numeroArrete, numero_arrete=numero_arrete,
code_vdi=VDICode, code_vdi=code_vdi,
date_jury=date_jury, date_jury=date_jury,
show_title=showTitle, show_title=show_title,
pv_title=pv_title, pv_title=pv_title,
with_paragraph_nom=with_paragraph_nom, with_paragraph_nom=with_paragraph_nom,
anonymous=anonymous, anonymous=anonymous,
) )
if not objects:
return b""
jury_de_diplome = not dpv["semestre_non_terminal"] jury_de_diplome = formsemestre.est_terminal()
# Si Jury de passage et qu'un étudiant valide le parcours # Si Jury de passage et qu'un étudiant valide le parcours
# (car il a validé antérieurement le dernier semestre) # (car il a validé antérieurement le dernier semestre)
# alors on génère aussi un PV de diplome (à la suite dans le même doc PDF) # alors on génère aussi un PV de diplome (à la suite dans le même doc PDF)
if not jury_de_diplome: if not jury_de_diplome and a_diplome:
validations_parcours = [x["validation_parcours"] for x in dpv["decisions"]] # au moins un etudiant a validé son diplome:
if True in validations_parcours: objects.append(PageBreak())
# au moins un etudiant a validé son diplome: objects += _pvjury_pdf_type(
objects.append(PageBreak()) formsemestre,
objects += _pvjury_pdf_type( etudids,
dpv, only_diplome=True,
only_diplome=True, date_commission=date_commission,
date_commission=date_commission, date_jury=date_jury,
date_jury=date_jury, numero_arrete=numero_arrete,
numero_arrete=numeroArrete, code_vdi=code_vdi,
code_vdi=VDICode, show_title=show_title,
show_title=showTitle, pv_title=pv_title,
pv_title=pv_title, with_paragraph_nom=with_paragraph_nom,
with_paragraph_nom=with_paragraph_nom, anonymous=anonymous,
anonymous=anonymous, )[0]
)
# ----- Build PDF # ----- Build PDF
report = io.BytesIO() # in-memory document, no disk file report = io.BytesIO() # in-memory document, no disk file
@ -718,10 +714,10 @@ def pvjury_pdf(
document.addPageTemplates( document.addPageTemplates(
PVTemplate( PVTemplate(
document, document,
author="%s %s (E. Viennet)" % (sco_version.SCONAME, sco_version.SCOVERSION), author=f"{sco_version.SCONAME} {sco_version.SCOVERSION} (E. Viennet)",
title=SU("PV du jury de %s" % sem["titre_num"]), title=SU(f"PV du jury de {formsemestre.titre_num()}"),
subject="PV jury", subject="PV jury",
preferences=sco_preferences.SemPreferences(formsemestre_id), preferences=sco_preferences.SemPreferences(formsemestre.id),
) )
) )
@ -756,7 +752,8 @@ def _make_pv_styles(formsemestre: FormSemestre):
def _pvjury_pdf_type( def _pvjury_pdf_type(
dpv, formsemestre: FormSemestre,
etudids: list[int],
only_diplome=False, only_diplome=False,
date_commission=None, date_commission=None,
date_jury=None, date_jury=None,
@ -766,21 +763,19 @@ def _pvjury_pdf_type(
pv_title=None, pv_title=None,
anonymous=False, anonymous=False,
with_paragraph_nom=False, with_paragraph_nom=False,
): ) -> tuple[list, bool]:
"""Doc PDF récapitulant les décisions de jury pour un type de jury (passage ou delivrance) """Objets platypus PDF récapitulant les décisions de jury
dpv: result of dict_pvjury pour un type de jury (passage ou delivrance).
Ramene: liste d'onj platypus, et un boolen indiquant si au moins un étudiant est diplômé.
""" """
from app.scodoc import sco_pvjury from app.scodoc import sco_pvjury
from app.but import jury_but_pv from app.but import jury_but_pv
# Jury de diplome si sem. terminal OU que l'on demande les diplomés d'un semestre antérieur a_diplome = False
diplome = (not dpv["semestre_non_terminal"]) or only_diplome # Jury de diplome si sem. terminal OU que l'on demande seulement les diplomés
diplome = formsemestre.est_terminal() or only_diplome
sem = dpv["formsemestre"]
formsemestre_id = sem["formsemestre_id"]
formsemestre: FormSemestre = FormSemestre.query.get(formsemestre_id)
titre_jury, _ = _descr_jury(formsemestre, diplome) titre_jury, _ = _descr_jury(formsemestre, diplome)
titre_diplome = pv_title or dpv["formation"]["titre_officiel"] titre_diplome = pv_title or formsemestre.formation.titre_officiel
objects = [] objects = []
style, style_bullet = _make_pv_styles(formsemestre) style, style_bullet = _make_pv_styles(formsemestre)
@ -789,8 +784,8 @@ def _pvjury_pdf_type(
objects += sco_pdf.make_paras( objects += sco_pdf.make_paras(
f""" f"""
<para align="center"><b>Procès-verbal de {titre_jury} du département { <para align="center"><b>Procès-verbal de {titre_jury} du département {
sco_preferences.get_preference("DeptName", formsemestre_id) or "(sans nom)" sco_preferences.get_preference("DeptName", formsemestre.id) or "(sans nom)"
} - Session unique {sem["anneescolaire"]}</b></para> } - Session unique {formsemestre.annee_scolaire()}</b></para>
""", """,
style, style,
) )
@ -805,7 +800,7 @@ def _pvjury_pdf_type(
f"""<para align="center"><b>Semestre: {formsemestre.titre}</b></para>""", f"""<para align="center"><b>Semestre: {formsemestre.titre}</b></para>""",
style, style,
) )
if sco_preferences.get_preference("PV_TITLE_WITH_VDI", formsemestre_id): if sco_preferences.get_preference("PV_TITLE_WITH_VDI", formsemestre.id):
objects += sco_pdf.make_paras( objects += sco_pdf.make_paras(
f"""<para align="center">VDI et Code: {(code_vdi or "")}</para>""", style f"""<para align="center">VDI et Code: {(code_vdi or "")}</para>""", style
) )
@ -817,11 +812,11 @@ def _pvjury_pdf_type(
objects += sco_pdf.make_paras( objects += sco_pdf.make_paras(
"<para>" "<para>"
+ (sco_preferences.get_preference("PV_INTRO", formsemestre_id) or "") + (sco_preferences.get_preference("PV_INTRO", formsemestre.id) or "")
% { % {
"Decnum": numero_arrete, "Decnum": numero_arrete,
"VDICode": code_vdi, "VDICode": code_vdi,
"UnivName": sco_preferences.get_preference("UnivName", formsemestre_id), "UnivName": sco_preferences.get_preference("UnivName", formsemestre.id),
"Type": titre_jury, "Type": titre_jury,
"Date": date_commission, # deprecated "Date": date_commission, # deprecated
"date_commission": date_commission, "date_commission": date_commission,
@ -836,15 +831,24 @@ def _pvjury_pdf_type(
objects += [Spacer(0, 4 * mm)] objects += [Spacer(0, 4 * mm)]
if formsemestre.formation.is_apc(): if formsemestre.formation.is_apc():
rows, titles = jury_but_pv.pvjury_table_but(formsemestre) rows, titles = jury_but_pv.pvjury_table_but(
formsemestre, etudids=etudids, line_sep="<br/>"
)
columns_ids = list(titles.keys()) columns_ids = list(titles.keys())
a_diplome = codes_cursus.ADM in [row.get("diplome") for row in rows]
else: else:
dpv = sco_dict_pv_jury.dict_pvjury(
formsemestre.id, etudids=etudids, with_prev=True
)
if not dpv:
return [], False
rows, titles, columns_ids = sco_pvjury.pvjury_table( rows, titles, columns_ids = sco_pvjury.pvjury_table(
dpv, dpv,
only_diplome=only_diplome, only_diplome=only_diplome,
anonymous=anonymous, anonymous=anonymous,
with_paragraph_nom=with_paragraph_nom, with_paragraph_nom=with_paragraph_nom,
) )
a_diplome = True in (x["validation_parcours"] for x in dpv["decisions"])
# convert to lists of tuples: # convert to lists of tuples:
columns_ids = ["etudid"] + columns_ids columns_ids = ["etudid"] + columns_ids
rows = [[line.get(x, "") for x in columns_ids] for line in rows] rows = [[line.get(x, "") for x in columns_ids] for line in rows]
@ -852,11 +856,11 @@ def _pvjury_pdf_type(
# Make a new cell style and put all cells in paragraphs # Make a new cell style and put all cells in paragraphs
cell_style = styles.ParagraphStyle({}) cell_style = styles.ParagraphStyle({})
cell_style.fontSize = sco_preferences.get_preference( cell_style.fontSize = sco_preferences.get_preference(
"SCOLAR_FONT_SIZE", formsemestre_id "SCOLAR_FONT_SIZE", formsemestre.id
) )
cell_style.fontName = sco_preferences.get_preference("PV_FONTNAME", formsemestre_id) cell_style.fontName = sco_preferences.get_preference("PV_FONTNAME", formsemestre.id)
cell_style.leading = 1.0 * sco_preferences.get_preference( cell_style.leading = 1.0 * sco_preferences.get_preference(
"SCOLAR_FONT_SIZE", formsemestre_id "SCOLAR_FONT_SIZE", formsemestre.id
) # vertical space ) # vertical space
LINEWIDTH = 0.5 LINEWIDTH = 0.5
table_style = [ table_style = [
@ -864,7 +868,7 @@ def _pvjury_pdf_type(
"FONTNAME", "FONTNAME",
(0, 0), (0, 0),
(-1, 0), (-1, 0),
sco_preferences.get_preference("PV_FONTNAME", formsemestre_id), sco_preferences.get_preference("PV_FONTNAME", formsemestre.id),
), ),
("LINEBELOW", (0, 0), (-1, 0), LINEWIDTH, Color(0, 0, 0)), ("LINEBELOW", (0, 0), (-1, 0), LINEWIDTH, Color(0, 0, 0)),
("GRID", (0, 0), (-1, -1), LINEWIDTH, Color(0, 0, 0)), ("GRID", (0, 0), (-1, -1), LINEWIDTH, Color(0, 0, 0)),
@ -889,10 +893,7 @@ def _pvjury_pdf_type(
table_cells = [[_format_pv_cell(x) for x in line[1:]] for line in ([titles] + rows)] table_cells = [[_format_pv_cell(x) for x in line[1:]] for line in ([titles] + rows)]
widths = [widths_by_id.get(col_id) for col_id in columns_ids[1:]] widths = [widths_by_id.get(col_id) for col_id in columns_ids[1:]]
# if dpv["has_prev"]:
# widths[2:2] = [2.8 * cm]
# if sco_preferences.get_preference("bul_show_mention", formsemestre_id):
# widths += [None]
objects.append( objects.append(
Table(table_cells, repeatRows=1, colWidths=widths, style=table_style) Table(table_cells, repeatRows=1, colWidths=widths, style=table_style)
) )
@ -900,9 +901,9 @@ def _pvjury_pdf_type(
# Signature du directeur # Signature du directeur
objects += sco_pdf.make_paras( objects += sco_pdf.make_paras(
f"""<para spaceBefore="10mm" align="right">{ f"""<para spaceBefore="10mm" align="right">{
sco_preferences.get_preference("DirectorName", formsemestre_id) or "" sco_preferences.get_preference("DirectorName", formsemestre.id) or ""
}, { }, {
sco_preferences.get_preference("DirectorTitle", formsemestre_id) or "" sco_preferences.get_preference("DirectorTitle", formsemestre.id) or ""
}</para>""", }</para>""",
style, style,
) )
@ -923,7 +924,7 @@ def _pvjury_pdf_type(
"FONTNAME", "FONTNAME",
(0, 0), (0, 0),
(-1, 0), (-1, 0),
sco_preferences.get_preference("PV_FONTNAME", formsemestre_id), sco_preferences.get_preference("PV_FONTNAME", formsemestre.id),
), ),
("LINEBELOW", (0, 0), (-1, -1), LINEWIDTH, Color(0, 0, 0)), ("LINEBELOW", (0, 0), (-1, -1), LINEWIDTH, Color(0, 0, 0)),
("LINEABOVE", (0, 0), (-1, -1), LINEWIDTH, Color(0, 0, 0)), ("LINEABOVE", (0, 0), (-1, -1), LINEWIDTH, Color(0, 0, 0)),
@ -938,4 +939,4 @@ def _pvjury_pdf_type(
) )
) )
return objects return objects, a_diplome