From 9e52566781822e99b29dfd456a893c313f747285 Mon Sep 17 00:00:00 2001 From: Emmanuel Viennet Date: Sun, 19 Feb 2023 02:54:29 +0100 Subject: [PATCH] =?UTF-8?q?PV=20Jury=20PDF:=20refactoring,=20optimisation,?= =?UTF-8?q?=20am=C3=A9lioration.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/but/jury_but.py | 4 +- app/but/jury_but_pv.py | 16 +- app/but/jury_but_results.py | 4 +- app/models/formsemestre.py | 6 + app/scodoc/sco_archives.py | 48 ++-- app/scodoc/sco_bulletins.py | 6 +- app/scodoc/sco_dict_pv_jury.py | 324 +++++++++++++++++++++ app/scodoc/sco_export_results.py | 7 +- app/scodoc/sco_formsemestre_validation.py | 4 +- app/scodoc/sco_inscr_passage.py | 4 +- app/scodoc/sco_pvjury.py | 328 ++-------------------- app/scodoc/sco_pvpdf.py | 139 ++++----- 12 files changed, 469 insertions(+), 421 deletions(-) create mode 100644 app/scodoc/sco_dict_pv_jury.py diff --git a/app/but/jury_but.py b/app/but/jury_but.py index c2143019a1..9fe5ed9378 100644 --- a/app/but/jury_but.py +++ b/app/but/jury_but.py @@ -1154,7 +1154,7 @@ class DecisionsProposeesRCUE(DecisionsProposees): def descr_validation(self) -> str: """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 ( @@ -1164,7 +1164,7 @@ class DecisionsProposeesRCUE(DecisionsProposees): ordre = self.rcue.ue_1.niveau_competence.ordre else: return "?" # oups ? - return f"{niveau_titre} niv. {ordre}" + return f"{niveau_titre}-{ordre}" return "" diff --git a/app/but/jury_but_pv.py b/app/but/jury_but_pv.py index c01d4c5382..b3ffba7799 100644 --- a/app/but/jury_but_pv.py +++ b/app/but/jury_but_pv.py @@ -44,8 +44,7 @@ def pvjury_page_but(formsemestre_id: int, fmt="html"): """ formsemestre: FormSemestre = FormSemestre.query.get_or_404(formsemestre_id) assert formsemestre.formation.is_apc() - title = "Procès-verbal de jury BUT annuel" - + title = "Procès-verbal de jury BUT" if fmt == "html": line_sep = "
" else: @@ -93,9 +92,11 @@ def pvjury_page_but(formsemestre_id: int, fmt="html"): def pvjury_table_but( - formsemestre: FormSemestre, line_sep: str = "\n" + formsemestre: FormSemestre, etudids: list[int] = None, line_sep: str = "\n" ) -> 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 annee_but = (formsemestre.semestre_id + 1) // 2 titles = { @@ -110,7 +111,12 @@ def pvjury_table_but( "observations": "Observations", } 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) try: deca = jury_but.DecisionsProposeesAnnee(etud, formsemestre) diff --git a/app/but/jury_but_results.py b/app/but/jury_but_results.py index 79aa14df33..d947bf8456 100644 --- a/app/but/jury_but_results.py +++ b/app/but/jury_but_results.py @@ -12,7 +12,7 @@ import numpy as np from app.but import jury_but from app.models.etudiants import Identite 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]: @@ -20,7 +20,7 @@ def get_jury_but_results(formsemestre: FormSemestre) -> list[dict]: if formsemestre.formation.referentiel_competence is None: # pas de ref. comp., donc pas de decisions de jury (ne lance pas d'exception) return [] - dpv = sco_pvjury.dict_pvjury(formsemestre.id) + dpv = sco_dict_pv_jury.dict_pvjury(formsemestre.id) rows = [] for etudid in formsemestre.etuds_inscriptions: rows.append(_get_jury_but_etud_result(formsemestre, dpv, etudid)) diff --git a/app/models/formsemestre.py b/app/models/formsemestre.py index 3b01ff96a2..9815b9fd4a 100644 --- a/app/models/formsemestre.py +++ b/app/models/formsemestre.py @@ -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 def comp_periode( cls, diff --git a/app/scodoc/sco_archives.py b/app/scodoc/sco_archives.py index aa57823a20..3e3ff43aa3 100644 --- a/app/scodoc/sco_archives.py +++ b/app/scodoc/sco_archives.py @@ -71,14 +71,14 @@ from app.comp import res_sem from app.comp.res_compat import NotesTableCompat from app.models import Departement, FormSemestre 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 sco_bulletins_pdf from app.scodoc import sco_formsemestre from app.scodoc import sco_groups 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_dict_pv_jury from app.scodoc import sco_pvpdf from app.scodoc.sco_exceptions import ScoValueError @@ -299,13 +299,13 @@ def do_formsemestre_archive( date_jury="", signature=None, # pour lettres indiv date_commission=None, - numeroArrete=None, - VDICode=None, - showTitle=False, + numero_arrete=None, + code_vdi=None, + show_title=False, pv_title=None, with_paragraph_nom=False, anonymous=False, - bulVersion="long", + bul_version="long", ): """Make and store new archive for this formsemestre. Store: @@ -377,7 +377,7 @@ def do_formsemestre_archive( ) # Classeur bulletins (PDF) data, _ = sco_bulletins_pdf.get_formsemestre_bulletins_pdf( - formsemestre_id, version=bulVersion + formsemestre_id, version=bul_version ) if data: PVArchive.store(archive_id, "Bulletins.pdf", data) @@ -395,14 +395,16 @@ def do_formsemestre_archive( # PV de jury (PDF): disponible seulement en classique # en BUT, le PV est sous forme excel (Decisions_Jury.xlsx ci-dessus) 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( dpv, date_commission=date_commission, date_jury=date_jury, - numeroArrete=numeroArrete, - VDICode=VDICode, - showTitle=showTitle, + numero_arrete=numero_arrete, + code_vdi=code_vdi, + show_title=show_title, pv_title=pv_title, with_paragraph_nom=with_paragraph_nom, anonymous=anonymous, @@ -411,7 +413,7 @@ def do_formsemestre_archive( 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. (all students or only selected groups) """ @@ -424,8 +426,6 @@ def formsemestre_archive(formsemestre_id, group_ids=[]): formsemestre_id=formsemestre_id, ) ) - - sem = sco_formsemestre.get_formsemestre(formsemestre_id) if not group_ids: # tous les inscrits du semestre 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"}), ] - descr += sco_pvjury.descrform_pvjury(sem) + descr += sco_pvjury.descrform_pvjury(formsemestre) descr += [ ( "signature", @@ -473,7 +473,7 @@ enregistrés et non modifiables, on peut les retrouver ultérieurement. }, ), ( - "bulVersion", + "bul_version", { "input_type": "menu", "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_commission=tf[2]["date_commission"], signature=signature, - numeroArrete=tf[2]["numeroArrete"], - VDICode=tf[2]["VDICode"], + numero_arrete=tf[2]["numero_arrete"], + code_vdi=tf[2]["code_vdi"], pv_title=tf[2]["pv_title"], - showTitle=tf[2]["showTitle"], + show_title=tf[2]["show_title"], with_paragraph_nom=tf[2]["with_paragraph_nom"], anonymous=tf[2]["anonymous"], - bulVersion=tf[2]["bulVersion"], + bul_version=tf[2]["bul_version"], ) msg = "Nouvelle%20archive%20créée" # submitted or cancelled: + flash(msg) return flask.redirect( - "formsemestre_list_archives?formsemestre_id=%s&head_message=%s" - % (formsemestre_id, msg) + url_for( + "notes.formsemestre_list_archives", + scodoc_dept=g.scodoc_dept, + formsemestre_id=formsemestre_id, + ) ) diff --git a/app/scodoc/sco_bulletins.py b/app/scodoc/sco_bulletins.py index cef2c73ec1..84e810a75d 100644 --- a/app/scodoc/sco_bulletins.py +++ b/app/scodoc/sco_bulletins.py @@ -56,12 +56,10 @@ from app.scodoc import sco_bulletins_xml from app.scodoc import codes_cursus from app.scodoc import sco_etud from app.scodoc import sco_evaluation_db -from app.scodoc import sco_formations from app.scodoc import sco_formsemestre from app.scodoc import sco_groups -from app.scodoc import sco_permissions_check 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 import app.scodoc.sco_utils as scu from app.scodoc.sco_utils import ModuleType, fmt_note @@ -789,7 +787,7 @@ def etud_descr_situation_semestre( infos["date_defaillance"] = date_def 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: infos["decision_sem"] = dpv["decisions"][0]["decision_sem"] diff --git a/app/scodoc/sco_dict_pv_jury.py b/app/scodoc/sco_dict_pv_jury.py new file mode 100644 index 0000000000..1075a2ce3b --- /dev/null +++ b/app/scodoc/sco_dict_pv_jury.py @@ -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 diff --git a/app/scodoc/sco_export_results.py b/app/scodoc/sco_export_results.py index 1ad0f3706a..4d27ec1aab 100644 --- a/app/scodoc/sco_export_results.py +++ b/app/scodoc/sco_export_results.py @@ -39,9 +39,8 @@ from app.models import Formation from app.scodoc import html_sco_header from app.scodoc import sco_bac from app.scodoc import codes_cursus -from app.scodoc import sco_formations 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 import sco_version 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: dpv_by_sem = {} 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 ) @@ -349,7 +348,7 @@ end_date='2017-08-31' formsemestre_ids = get_set_formsemestre_id_dates( start_date, end_date) dpv_by_sem = {} 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() ] diff --git a/app/scodoc/sco_formsemestre_validation.py b/app/scodoc/sco_formsemestre_validation.py index 15e67fa5e6..49ca5de083 100644 --- a/app/scodoc/sco_formsemestre_validation.py +++ b/app/scodoc/sco_formsemestre_validation.py @@ -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 import sco_photos 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( @@ -562,7 +562,7 @@ def formsemestre_recap_parcours_table( is_cur = Se.formsemestre_id == sem["formsemestre_id"] 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] decision_sem = pv["decision_sem"] decisions_ue = pv["decisions_ue"] diff --git a/app/scodoc/sco_inscr_passage.py b/app/scodoc/sco_inscr_passage.py index b26701f692..197b5169ed 100644 --- a/app/scodoc/sco_inscr_passage.py +++ b/app/scodoc/sco_inscr_passage.py @@ -47,7 +47,7 @@ from app.scodoc import sco_formsemestre from app.scodoc import sco_formsemestre_inscriptions from app.scodoc import sco_groups 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 @@ -137,7 +137,7 @@ def list_inscrits(formsemestre_id, with_dems=False): def list_etuds_from_sem(src, dst) -> list[dict]: """Liste des etudiants du semestre src qui sont autorisés à passer dans le semestre dst.""" 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: return [] etuds = [ diff --git a/app/scodoc/sco_pvjury.py b/app/scodoc/sco_pvjury.py index d1bd0e0b55..6ec676a4aa 100644 --- a/app/scodoc/sco_pvjury.py +++ b/app/scodoc/sco_pvjury.py @@ -47,7 +47,6 @@ Jury de semestre n """ import time -from operator import itemgetter from reportlab.platypus import Paragraph from reportlab.lib import styles @@ -55,25 +54,18 @@ import flask from flask import flash, redirect, url_for from flask import g, request -from app.comp import res_sem -from app.comp.res_compat import NotesTableCompat from app.models import ( Formation, FormSemestre, - UniteEns, ScolarAutorisationInscription, - but_validations, ) from app.models.etudiants import Identite import app.scodoc.sco_utils as scu import app.scodoc.notesdb as ndb -from app import log from app.scodoc import html_sco_header from app.scodoc import codes_cursus -from app.scodoc import sco_cursus -from app.scodoc import sco_cursus_dut -from app.scodoc import sco_edit_ue +from app.scodoc import sco_dict_pv_jury from app.scodoc import sco_etud from app.scodoc import sco_formsemestre from app.scodoc import sco_groups @@ -87,57 +79,6 @@ from app.scodoc.sco_pdf import PDFLOCK 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): "résumé textuel tres court (code) de la décision de semestre" if etat == "D": @@ -150,230 +91,6 @@ def _descr_decision_sem_abbrev(etat, decision_sem): 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( dpv, only_diplome=False, @@ -420,7 +137,7 @@ def pvjury_table( if dpv["has_prev"]: 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"] 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() - dpv = dict_pvjury(formsemestre_id, with_prev=True) + dpv = sco_dict_pv_jury.dict_pvjury(formsemestre_id, with_prev=True) if not dpv: if format == "html": 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é. """ 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: sco_groups.create_etapes_partition(formsemestre_id) groups_infos = None @@ -671,7 +388,7 @@ def formsemestre_pvjury_pdf(formsemestre_id, group_ids: list[int] = None, etudid

""", html_sco_header.sco_footer(), ] - descr = descrform_pvjury(sem) + descr = descrform_pvjury(formsemestre) if etudid: descr.append(("etudid", {"input_type": "hidden"})) @@ -706,43 +423,36 @@ def formsemestre_pvjury_pdf(formsemestre_id, group_ids: list[int] = None, etudid ) else: # submit - dpv = dict_pvjury(formsemestre_id, etudids=etudids, with_prev=True) - if tf[2]["showTitle"]: - tf[2]["showTitle"] = True - else: - tf[2]["showTitle"] = False - if tf[2]["anonymous"]: - tf[2]["anonymous"] = True - else: - tf[2]["anonymous"] = False + tf[2]["show_title"] = bool(tf[2]["show_title"]) + tf[2]["anonymous"] = bool(tf[2]["anonymous"]) try: PDFLOCK.acquire() pdfdoc = sco_pvpdf.pvjury_pdf( - dpv, - numeroArrete=tf[2]["numeroArrete"], - VDICode=tf[2]["VDICode"], + formsemestre, + etudids, + numero_arrete=tf[2]["numero_arrete"], + code_vdi=tf[2]["code_vdi"], date_commission=tf[2]["date_commission"], date_jury=tf[2]["date_jury"], - showTitle=tf[2]["showTitle"], + show_title=tf[2]["show_title"], pv_title=tf[2]["pv_title"], with_paragraph_nom=tf[2]["with_paragraph_nom"], anonymous=tf[2]["anonymous"], ) finally: PDFLOCK.release() - sem = sco_formsemestre.get_formsemestre(formsemestre_id) date_iso = time.strftime("%Y-%m-%d") if groups_infos: groups_filename = "-" + groups_infos.groups_filename else: 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) -def descrform_pvjury(sem): +def descrform_pvjury(formsemestre: FormSemestre): """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 [ ( "date_commission", @@ -763,7 +473,7 @@ def descrform_pvjury(sem): }, ), ( - "numeroArrete", + "numero_arrete", { "input_type": "text", "size": 50, @@ -772,7 +482,7 @@ def descrform_pvjury(sem): }, ), ( - "VDICode", + "code_vdi", { "input_type": "text", "size": 15, @@ -791,11 +501,11 @@ def descrform_pvjury(sem): }, ), ( - "showTitle", + "show_title", { "input_type": "checkbox", "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": [""], "allowed_values": ("1",), }, diff --git a/app/scodoc/sco_pvpdf.py b/app/scodoc/sco_pvpdf.py index eb0dfd4197..deeea3933f 100644 --- a/app/scodoc/sco_pvpdf.py +++ b/app/scodoc/sco_pvpdf.py @@ -49,6 +49,7 @@ from app.models import FormSemestre, Identite import app.scodoc.sco_utils as scu from app.scodoc import sco_bulletins_pdf 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_pdf 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) Renvoie pdf data ou chaine vide si aucun etudiant avec décision de jury. """ - from app.scodoc import sco_pvjury - - 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) if not dpv: return "" # Ajoute infos sur etudiants @@ -656,60 +655,57 @@ def add_apc_infos(formsemestre: FormSemestre, params: dict, decision: dict): # ---------------------------------------------- def pvjury_pdf( - dpv, + formsemestre: FormSemestre, + etudids: list[int], date_commission=None, date_jury=None, - numeroArrete=None, - VDICode=None, - showTitle=False, + numero_arrete=None, + code_vdi=None, + show_title=False, pv_title=None, with_paragraph_nom=False, anonymous=False, -): +) -> bytes: """Doc PDF récapitulant les décisions de jury (tableau en format paysage) - dpv: result of dict_pvjury """ - if not dpv: - return {} - sem = dpv["formsemestre"] - formsemestre_id = sem["formsemestre_id"] - - objects = _pvjury_pdf_type( - dpv, + objects, a_diplome = _pvjury_pdf_type( + formsemestre, + etudids, only_diplome=False, date_commission=date_commission, - numero_arrete=numeroArrete, - code_vdi=VDICode, + numero_arrete=numero_arrete, + code_vdi=code_vdi, date_jury=date_jury, - show_title=showTitle, + show_title=show_title, pv_title=pv_title, with_paragraph_nom=with_paragraph_nom, 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 # (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) - if not jury_de_diplome: - validations_parcours = [x["validation_parcours"] for x in dpv["decisions"]] - if True in validations_parcours: - # au moins un etudiant a validé son diplome: - objects.append(PageBreak()) - objects += _pvjury_pdf_type( - dpv, - only_diplome=True, - date_commission=date_commission, - date_jury=date_jury, - numero_arrete=numeroArrete, - code_vdi=VDICode, - show_title=showTitle, - pv_title=pv_title, - with_paragraph_nom=with_paragraph_nom, - anonymous=anonymous, - ) + if not jury_de_diplome and a_diplome: + # au moins un etudiant a validé son diplome: + objects.append(PageBreak()) + objects += _pvjury_pdf_type( + formsemestre, + etudids, + only_diplome=True, + date_commission=date_commission, + date_jury=date_jury, + numero_arrete=numero_arrete, + code_vdi=code_vdi, + show_title=show_title, + pv_title=pv_title, + with_paragraph_nom=with_paragraph_nom, + anonymous=anonymous, + )[0] # ----- Build PDF report = io.BytesIO() # in-memory document, no disk file @@ -718,10 +714,10 @@ def pvjury_pdf( document.addPageTemplates( PVTemplate( document, - author="%s %s (E. Viennet)" % (sco_version.SCONAME, sco_version.SCOVERSION), - title=SU("PV du jury de %s" % sem["titre_num"]), + author=f"{sco_version.SCONAME} {sco_version.SCOVERSION} (E. Viennet)", + title=SU(f"PV du jury de {formsemestre.titre_num()}"), 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( - dpv, + formsemestre: FormSemestre, + etudids: list[int], only_diplome=False, date_commission=None, date_jury=None, @@ -766,21 +763,19 @@ def _pvjury_pdf_type( pv_title=None, anonymous=False, with_paragraph_nom=False, -): - """Doc PDF récapitulant les décisions de jury pour un type de jury (passage ou delivrance) - dpv: result of dict_pvjury +) -> tuple[list, bool]: + """Objets platypus PDF récapitulant les décisions de jury + 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.but import jury_but_pv - # Jury de diplome si sem. terminal OU que l'on demande les diplomés d'un semestre antérieur - diplome = (not dpv["semestre_non_terminal"]) or only_diplome - - sem = dpv["formsemestre"] - formsemestre_id = sem["formsemestre_id"] - formsemestre: FormSemestre = FormSemestre.query.get(formsemestre_id) + a_diplome = False + # Jury de diplome si sem. terminal OU que l'on demande seulement les diplomés + diplome = formsemestre.est_terminal() or only_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 = [] style, style_bullet = _make_pv_styles(formsemestre) @@ -789,8 +784,8 @@ def _pvjury_pdf_type( objects += sco_pdf.make_paras( f""" Procès-verbal de {titre_jury} du département { - sco_preferences.get_preference("DeptName", formsemestre_id) or "(sans nom)" - } - Session unique {sem["anneescolaire"]} + sco_preferences.get_preference("DeptName", formsemestre.id) or "(sans nom)" + } - Session unique {formsemestre.annee_scolaire()} """, style, ) @@ -805,7 +800,7 @@ def _pvjury_pdf_type( f"""Semestre: {formsemestre.titre}""", 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( f"""VDI et Code: {(code_vdi or "")}""", style ) @@ -817,11 +812,11 @@ def _pvjury_pdf_type( objects += sco_pdf.make_paras( "" - + (sco_preferences.get_preference("PV_INTRO", formsemestre_id) or "") + + (sco_preferences.get_preference("PV_INTRO", formsemestre.id) or "") % { "Decnum": numero_arrete, "VDICode": code_vdi, - "UnivName": sco_preferences.get_preference("UnivName", formsemestre_id), + "UnivName": sco_preferences.get_preference("UnivName", formsemestre.id), "Type": titre_jury, "Date": date_commission, # deprecated "date_commission": date_commission, @@ -836,15 +831,24 @@ def _pvjury_pdf_type( objects += [Spacer(0, 4 * mm)] 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="
" + ) columns_ids = list(titles.keys()) + a_diplome = codes_cursus.ADM in [row.get("diplome") for row in rows] 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( dpv, only_diplome=only_diplome, anonymous=anonymous, with_paragraph_nom=with_paragraph_nom, ) + a_diplome = True in (x["validation_parcours"] for x in dpv["decisions"]) # convert to lists of tuples: columns_ids = ["etudid"] + columns_ids 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 cell_style = styles.ParagraphStyle({}) 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( - "SCOLAR_FONT_SIZE", formsemestre_id + "SCOLAR_FONT_SIZE", formsemestre.id ) # vertical space LINEWIDTH = 0.5 table_style = [ @@ -864,7 +868,7 @@ def _pvjury_pdf_type( "FONTNAME", (0, 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)), ("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)] 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( Table(table_cells, repeatRows=1, colWidths=widths, style=table_style) ) @@ -900,9 +901,9 @@ def _pvjury_pdf_type( # Signature du directeur objects += sco_pdf.make_paras( f"""{ - 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 "" }""", style, ) @@ -923,7 +924,7 @@ def _pvjury_pdf_type( "FONTNAME", (0, 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)), ("LINEABOVE", (0, 0), (-1, -1), LINEWIDTH, Color(0, 0, 0)), @@ -938,4 +939,4 @@ def _pvjury_pdf_type( ) ) - return objects + return objects, a_diplome