# -*- 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 # ############################################################################## """Edition des PV de jury """ import io import re from PIL import Image as PILImage from PIL import UnidentifiedImageError import reportlab from reportlab.lib.units import cm, mm from reportlab.lib.enums import TA_LEFT, TA_RIGHT, TA_JUSTIFY from reportlab.platypus import Paragraph, Spacer, Frame, PageBreak from reportlab.platypus import Table, TableStyle, Image from reportlab.platypus.doctemplate import PageTemplate, BaseDocTemplate from reportlab.lib.pagesizes import A4, landscape from reportlab.lib import styles from reportlab.lib.colors import Color from flask import g 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 from app.scodoc.sco_exceptions import ScoValueError from app.scodoc.sco_logos import find_logo from app.scodoc.sco_cursus_dut import SituationEtudCursus from app.scodoc.sco_pdf import SU import sco_version LOGO_FOOTER_ASPECT = scu.CONFIG.LOGO_FOOTER_ASPECT # XXX A AUTOMATISER LOGO_FOOTER_HEIGHT = scu.CONFIG.LOGO_FOOTER_HEIGHT * mm LOGO_FOOTER_WIDTH = LOGO_FOOTER_HEIGHT * scu.CONFIG.LOGO_FOOTER_ASPECT LOGO_HEADER_ASPECT = scu.CONFIG.LOGO_HEADER_ASPECT # XXX logo IUTV (A AUTOMATISER) LOGO_HEADER_HEIGHT = scu.CONFIG.LOGO_HEADER_HEIGHT * mm LOGO_HEADER_WIDTH = LOGO_HEADER_HEIGHT * scu.CONFIG.LOGO_HEADER_ASPECT def page_footer(canvas, doc, logo, preferences, with_page_numbers=True): "Add footer on page" width = doc.pagesize[0] # - doc.pageTemplate.left_p - doc.pageTemplate.right_p foot = Frame( 0.1 * mm, 0.2 * cm, width - 1 * mm, 2 * cm, leftPadding=0, rightPadding=0, topPadding=0, bottomPadding=0, id="monfooter", showBoundary=0, ) left_foot_style = reportlab.lib.styles.ParagraphStyle({}) left_foot_style.fontName = preferences["SCOLAR_FONT"] left_foot_style.fontSize = preferences["SCOLAR_FONT_SIZE_FOOT"] left_foot_style.leftIndent = 0 left_foot_style.firstLineIndent = 0 left_foot_style.alignment = TA_RIGHT right_foot_style = reportlab.lib.styles.ParagraphStyle({}) right_foot_style.fontName = preferences["SCOLAR_FONT"] right_foot_style.fontSize = preferences["SCOLAR_FONT_SIZE_FOOT"] right_foot_style.alignment = TA_RIGHT p = sco_pdf.make_paras( f"""{preferences["INSTITUTION_NAME"]}{ preferences["INSTITUTION_ADDRESS"]}""", left_foot_style, ) np = Paragraph(f'{doc.page}', right_foot_style) tabstyle = TableStyle( [ ("LEFTPADDING", (0, 0), (-1, -1), 0), ("RIGHTPADDING", (0, 0), (-1, -1), 0), ("ALIGN", (0, 0), (-1, -1), "RIGHT"), # ('INNERGRID', (0,0), (-1,-1), 0.25, black),#debug # ('LINEABOVE', (0,0), (-1,0), 0.5, black), ("VALIGN", (1, 0), (1, 0), "MIDDLE"), ("RIGHTPADDING", (-1, 0), (-1, 0), 1 * cm), ] ) elems = [p] if logo: elems.append(logo) colWidths = [None, LOGO_FOOTER_WIDTH + 2 * mm] if with_page_numbers: elems.append(np) colWidths.append(2 * cm) else: elems.append("") colWidths.append(8 * mm) # force marge droite tab = Table([elems], style=tabstyle, colWidths=colWidths) canvas.saveState() # is it necessary ? foot.addFromList([tab], canvas) canvas.restoreState() def page_header(canvas, doc, logo, preferences, only_on_first_page=False): "Ajoute au canvas le frame avec le logo" if only_on_first_page and int(doc.page) > 1: return height = doc.pagesize[1] head = Frame( -22 * mm, height - 13 * mm - LOGO_HEADER_HEIGHT, 10 * cm, LOGO_HEADER_HEIGHT + 2 * mm, leftPadding=0, rightPadding=0, topPadding=0, bottomPadding=0, id="monheader", showBoundary=0, ) if logo: canvas.saveState() # is it necessary ? head.addFromList([logo], canvas) canvas.restoreState() class CourrierIndividuelTemplate(PageTemplate): """Template pour courrier avisant des decisions de jury (1 page par étudiant)""" def __init__( self, document, pagesbookmarks=None, author=None, title=None, subject=None, margins=(0, 0, 0, 0), # additional margins in mm (left,top,right, bottom) preferences=None, # dictionnary with preferences, required force_header=False, force_footer=False, # always add a footer (whatever the preferences, use for PV) template_name="CourrierJuryTemplate", ): """Initialise our page template.""" self.pagesbookmarks = pagesbookmarks or {} self.pdfmeta_author = author self.pdfmeta_title = title self.pdfmeta_subject = subject self.preferences = preferences self.force_header = force_header self.force_footer = force_footer self.with_footer = ( self.force_footer or self.preferences["PV_LETTER_WITH_HEADER"] ) self.with_header = ( self.force_header or self.preferences["PV_LETTER_WITH_FOOTER"] ) self.with_page_background = self.preferences["PV_LETTER_WITH_BACKGROUND"] self.with_page_numbers = False self.header_only_on_first_page = False # Our doc is made of a single frame left, top, right, bottom = margins # marge additionnelle en mm # marges du Frame principal self.bot_p = 2 * cm self.left_p = 2.5 * cm self.right_p = 2.5 * cm self.top_p = 0 * cm # log("margins=%s" % str(margins)) content = Frame( self.left_p + left * mm, self.bot_p + bottom * mm, document.pagesize[0] - self.right_p - self.left_p - left * mm - right * mm, document.pagesize[1] - self.top_p - self.bot_p - top * mm - bottom * mm, ) PageTemplate.__init__(self, template_name, [content]) self.background_image_filename = None self.logo_footer = None self.logo_header = None # Search logos in dept specific dir, then in global scu.CONFIG dir if template_name == "PVJuryTemplate": background = find_logo( logoname="pvjury_background", dept_id=g.scodoc_dept_id, ) or find_logo( logoname="pvjury_background", dept_id=g.scodoc_dept_id, prefix="", ) else: background = find_logo( logoname="letter_background", dept_id=g.scodoc_dept_id, ) or find_logo( logoname="letter_background", dept_id=g.scodoc_dept_id, prefix="", ) if not self.background_image_filename and background is not None: self.background_image_filename = background.filepath footer = find_logo(logoname="footer", dept_id=g.scodoc_dept_id) if footer is not None: self.logo_footer = Image( footer.filepath, height=LOGO_FOOTER_HEIGHT, width=LOGO_FOOTER_WIDTH, ) header = find_logo(logoname="header", dept_id=g.scodoc_dept_id) if header is not None: self.logo_header = Image( header.filepath, height=LOGO_HEADER_HEIGHT, width=LOGO_HEADER_WIDTH, ) def beforeDrawPage(self, canv, doc): """Draws a logo and an contribution message on each page.""" # ---- Add some meta data and bookmarks if self.pdfmeta_author: canv.setAuthor(SU(self.pdfmeta_author)) if self.pdfmeta_title: canv.setTitle(SU(self.pdfmeta_title)) if self.pdfmeta_subject: canv.setSubject(SU(self.pdfmeta_subject)) bm = self.pagesbookmarks.get(doc.page, None) if bm != None: key = bm txt = SU(bm) canv.bookmarkPage(key) canv.addOutlineEntry(txt, bm) # ---- Background image if self.background_image_filename and self.with_page_background: canv.drawImage( self.background_image_filename, 0, 0, doc.pagesize[0], doc.pagesize[1] ) # ---- Header/Footer if self.with_header: page_header( canv, doc, self.logo_header, self.preferences, self.header_only_on_first_page, ) if self.with_footer: page_footer( canv, doc, self.logo_footer, self.preferences, with_page_numbers=self.with_page_numbers, ) class PVTemplate(CourrierIndividuelTemplate): """Template pour les pages des PV de jury""" def __init__( self, document, author=None, title=None, subject=None, margins=None, # additional margins in mm (left,top,right, bottom) preferences=None, # dictionnary with preferences, required ): if margins is None: margins = ( preferences["pv_left_margin"], preferences["pv_top_margin"], preferences["pv_right_margin"], preferences["pv_bottom_margin"], ) CourrierIndividuelTemplate.__init__( self, document, author=author, title=title, subject=subject, margins=margins, preferences=preferences, force_header=True, force_footer=True, template_name="PVJuryTemplate", ) self.with_page_numbers = True self.header_only_on_first_page = True self.with_header = self.preferences["PV_WITH_HEADER"] self.with_footer = self.preferences["PV_WITH_FOOTER"] self.with_page_background = self.preferences["PV_WITH_BACKGROUND"] def afterDrawPage(self, canv, doc): """Called after all flowables have been drawn on a page""" pass def beforeDrawPage(self, canv, doc): """Called before any flowables are drawn on a page""" # If the page number is even, force a page break CourrierIndividuelTemplate.beforeDrawPage(self, canv, doc) # Note: on cherche un moyen de generer un saut de page double # (redémarrer sur page impaire, nouvelle feuille en recto/verso). Pas trouvé en Platypus. # # if self.__pageNum % 2 == 0: # canvas.showPage() # # Increment pageNum again since we've added a blank page # self.__pageNum += 1 def _simulate_br(paragraph_txt: str, para="") -> str: """Reportlab bug turnaround (could be removed in a future version). p is a string with Reportlab intra-paragraph XML tags. Replaces
(currently ignored by Reportlab) by
Also replaces
by
""" return ("
" + para).join( re.split(r"<.*?br.*?/>", paragraph_txt.replace("
", "
")) ) def _make_signature_image(signature, leftindent, formsemestre_id) -> Table: "crée un paragraphe avec l'image signature" # cree une image PIL pour avoir la taille (W,H) f = io.BytesIO(signature) img = PILImage.open(f) width, height = img.size pdfheight = ( 1.0 * sco_preferences.get_preference("pv_sig_image_height", formsemestre_id) * mm ) f.seek(0, 0) style = styles.ParagraphStyle({}) style.leading = 1.0 * sco_preferences.get_preference( "SCOLAR_FONT_SIZE", formsemestre_id ) # vertical space style.leftIndent = leftindent return Table( [("", Image(f, width=width * pdfheight / float(height), height=pdfheight))], colWidths=(9 * cm, 7 * cm), ) def pdf_lettres_individuelles( formsemestre_id, etudids=None, date_jury="", date_commission="", signature=None, ): """Document PDF avec les lettres d'avis pour les etudiants mentionnés (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. """ dpv = sco_dict_pv_jury.dict_pvjury(formsemestre_id, etudids=etudids, with_prev=True) if not dpv: return "" # Ajoute infos sur etudiants etuds = [x["identite"] for x in dpv["decisions"]] sco_etud.fill_etuds_info(etuds) # formsemestre: FormSemestre = FormSemestre.query.get(formsemestre_id) prefs = sco_preferences.SemPreferences(formsemestre_id) params = { "date_jury": date_jury, "date_commission": date_commission, "titre_formation": dpv["formation"]["titre_officiel"], "htab1": "8cm", # lignes à droite (entete, signature) "htab2": "1cm", } # copie preferences for name in sco_preferences.get_base_preferences().prefs_name: params[name] = sco_preferences.get_preference(name, formsemestre_id) bookmarks = {} objects = [] # list of PLATYPUS objects npages = 0 for decision in dpv["decisions"]: if ( decision["decision_sem"] or decision.get("decision_annee") or decision.get("decision_rcue") ): # decision prise etud: Identite = Identite.query.get(decision["identite"]["etudid"]) params["nomEtud"] = etud.nomprenom bookmarks[npages + 1] = scu.suppress_accents(etud.nomprenom) try: objects += pdf_lettre_individuelle( dpv["formsemestre"], decision, etud, params, signature ) except UnidentifiedImageError as exc: raise ScoValueError( "Fichier image (signature ou logo ?) invalide !" ) from exc objects.append(PageBreak()) npages += 1 if npages == 0: return "" # Paramètres de mise en page margins = ( prefs["left_margin"], prefs["top_margin"], prefs["right_margin"], prefs["bottom_margin"], ) # ----- Build PDF report = io.BytesIO() # in-memory document, no disk file document = BaseDocTemplate(report) document.addPageTemplates( CourrierIndividuelTemplate( document, author=f"{sco_version.SCONAME} {sco_version.SCOVERSION} (E. Viennet)", title=f"Lettres décision {formsemestre.titre_annee()}", subject="Décision jury", margins=margins, pagesbookmarks=bookmarks, preferences=prefs, ) ) document.build(objects) data = report.getvalue() return data def _descr_jury(formsemestre: FormSemestre, diplome): if not diplome: if formsemestre.formation.is_apc(): t = f"""BUT{(formsemestre.semestre_id+1)//2}""" s = t else: t = f"""passage de Semestre {formsemestre.semestre_id} en Semestre {formsemestre.semestre_id + 1}""" s = "passage de semestre" else: t = "délivrance du diplôme" s = t return t, s # titre long, titre court def pdf_lettre_individuelle(sem, decision, etud: Identite, params, signature=None): """ Renvoie une liste d'objets PLATYPUS pour intégration dans un autre document. """ # formsemestre_id = sem["formsemestre_id"] formsemestre = FormSemestre.query.get(formsemestre_id) Se: SituationEtudCursus = decision["Se"] t, s = _descr_jury( formsemestre, Se.parcours_validated() or not Se.semestre_non_terminal ) objects = [] style = reportlab.lib.styles.ParagraphStyle({}) style.fontSize = 14 style.fontName = sco_preferences.get_preference("PV_FONTNAME", formsemestre_id) style.leading = 18 style.alignment = TA_LEFT params["semestre_id"] = formsemestre.semestre_id params["decision_sem_descr"] = decision["decision_sem_descr"] params["type_jury"] = t # type de jury (passage ou delivrance) params["type_jury_abbrv"] = s # idem, abbrégé params["decisions_ue_descr"] = decision["decisions_ue_descr"] if decision["decisions_ue_nb"] > 1: params["decisions_ue_descr_plural"] = "s" else: params["decisions_ue_descr_plural"] = "" params["INSTITUTION_CITY"] = ( sco_preferences.get_preference("INSTITUTION_CITY", formsemestre_id) or "" ) if decision["prev_decision_sem"]: params["prev_semestre_id"] = decision["prev"]["semestre_id"] params["prev_decision_sem_txt"] = "" params["decision_orig"] = "" params.update(decision["identite"]) # fix domicile if params["domicile"]: params["domicile"] = params["domicile"].replace("\\n", "
") # UE capitalisées: if decision["decisions_ue"] and decision["decisions_ue_descr"]: params["decision_ue_txt"] = ( """Unité%(decisions_ue_descr_plural)s d'Enseignement %(decision_orig)s capitalisée%(decisions_ue_descr_plural)s : %(decisions_ue_descr)s""" % params ) else: params["decision_ue_txt"] = "" # Mention params["mention"] = decision["mention"] # Informations sur compensations if decision["observation"]: params["observation_txt"] = ( """Observation : %(observation)s.""" % decision ) else: params["observation_txt"] = "" # Autorisations de passage if decision["autorisations"] and not Se.parcours_validated(): if len(decision["autorisations"]) > 1: s = "s" else: s = "" params[ "autorisations_txt" ] = """Vous êtes autorisé%s à continuer dans le%s semestre%s : %s""" % ( etud.e, s, s, decision["autorisations_descr"], ) else: params["autorisations_txt"] = "" if decision["decision_sem"] and Se.parcours_validated(): params["diplome_txt"] = ( """Vous avez donc obtenu le diplôme : %(titre_formation)s""" % params ) else: params["diplome_txt"] = "" # Les fonctions ci-dessous ajoutent ou modifient des champs: if formsemestre.formation.is_apc(): # ajout champs spécifiques PV BUT add_apc_infos(formsemestre, params, decision) else: # ajout champs spécifiques PV DUT add_classic_infos(formsemestre, params, decision) # Corps de la lettre: objects += sco_bulletins_pdf.process_field( sco_preferences.get_preference("PV_LETTER_TEMPLATE", sem["formsemestre_id"]), params, style, suppress_empty_pars=True, ) # Signature: # nota: si semestre terminal, signature par directeur IUT, sinon, signature par # chef de département. if Se.semestre_non_terminal: sig = ( sco_preferences.get_preference( "PV_LETTER_PASSAGE_SIGNATURE", formsemestre_id ) or "" ) % params sig = _simulate_br(sig, '') objects += sco_pdf.make_paras( ( """""" + sig + """""" ) % params, style, ) else: sig = ( sco_preferences.get_preference( "PV_LETTER_DIPLOMA_SIGNATURE", formsemestre_id ) or "" ) % params sig = _simulate_br(sig, '') objects += sco_pdf.make_paras( ( """""" + sig + """""" ) % params, style, ) if signature: try: objects.append( _make_signature_image(signature, params["htab1"], formsemestre_id) ) except UnidentifiedImageError as exc: raise ScoValueError("Image signature invalide !") from exc return objects def add_classic_infos(formsemestre: FormSemestre, params: dict, decision: dict): """Ajoute les champs pour les formations classiques, donc avec codes semestres""" if decision["prev_decision_sem"]: params["prev_code_descr"] = decision["prev_code_descr"] params[ "prev_decision_sem_txt" ] = f"""Décision du semestre antérieur S{params['prev_semestre_id']} : {params['prev_code_descr']}""" # Décision semestre courant: if formsemestre.semestre_id >= 0: params["decision_orig"] = f"du semestre S{formsemestre.semestre_id}" else: params["decision_orig"] = "" def add_apc_infos(formsemestre: FormSemestre, params: dict, decision: dict): """Ajoute les champs pour les formations APC (BUT), donc avec codes RCUE et année""" annee_but = (formsemestre.semestre_id + 1) // 2 params["decision_orig"] = f"année BUT{annee_but}" if decision is None: params["decision_sem_descr"] = "" params["decision_ue_txt"] = "" else: decision_annee = decision.get("decision_annee") or {} params["decision_sem_descr"] = decision_annee.get("code") or "" params[ "decision_ue_txt" ] = f"""{params["decision_ue_txt"]}
Niveaux de compétences:
{decision.get("descr_decisions_rcue") or ""} """ # ---------------------------------------------- def pvjury_pdf( formsemestre: FormSemestre, etudids: list[int], date_commission=None, date_jury=None, 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) """ objects, a_diplome = _pvjury_pdf_type( formsemestre, etudids, only_diplome=False, date_commission=date_commission, numero_arrete=numero_arrete, code_vdi=code_vdi, date_jury=date_jury, show_title=show_title, pv_title=pv_title, with_paragraph_nom=with_paragraph_nom, anonymous=anonymous, ) if not objects: return b"" 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 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 document = BaseDocTemplate(report) document.pagesize = landscape(A4) document.addPageTemplates( PVTemplate( document, 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), ) ) document.build(objects) data = report.getvalue() return data def _make_pv_styles(formsemestre: FormSemestre): style = reportlab.lib.styles.ParagraphStyle({}) style.fontSize = 12 style.fontName = sco_preferences.get_preference("PV_FONTNAME", formsemestre.id) style.leading = 18 style.alignment = TA_JUSTIFY indent = 1 * cm style_bullet = reportlab.lib.styles.ParagraphStyle({}) style_bullet.fontSize = 12 style_bullet.fontName = sco_preferences.get_preference( "PV_FONTNAME", formsemestre.id ) style_bullet.leading = 12 style_bullet.alignment = TA_JUSTIFY style_bullet.firstLineIndent = 0 style_bullet.leftIndent = indent style_bullet.bulletIndent = indent style_bullet.bulletFontName = "Times-Roman" style_bullet.bulletFontSize = 11 style_bullet.spaceBefore = 5 * mm style_bullet.spaceAfter = 5 * mm return style, style_bullet def _pvjury_pdf_type( formsemestre: FormSemestre, etudids: list[int], only_diplome=False, date_commission=None, date_jury=None, numero_arrete=None, code_vdi=None, show_title=False, pv_title=None, anonymous=False, with_paragraph_nom=False, ) -> 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 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 formsemestre.formation.titre_officiel objects = [] style, style_bullet = _make_pv_styles(formsemestre) objects += [Spacer(0, 5 * mm)] 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 {formsemestre.annee_scolaire()} """, style, ) objects += sco_pdf.make_paras( f"""{titre_diplome}""", style, ) if show_title: objects += sco_pdf.make_paras( f"""Semestre: {formsemestre.titre}""", style, ) if sco_preferences.get_preference("PV_TITLE_WITH_VDI", formsemestre.id): objects += sco_pdf.make_paras( f"""VDI et Code: {(code_vdi or "")}""", style ) if date_jury: objects += sco_pdf.make_paras( f"""Jury tenu le {date_jury}""", style ) objects += sco_pdf.make_paras( "" + (sco_preferences.get_preference("PV_INTRO", formsemestre.id) or "") % { "Decnum": numero_arrete, "VDICode": code_vdi, "UnivName": sco_preferences.get_preference("UnivName", formsemestre.id), "Type": titre_jury, "Date": date_commission, # deprecated "date_commission": date_commission, } + "", style_bullet, ) objects += sco_pdf.make_paras( """Le jury propose les décisions suivantes :""", style ) objects += [Spacer(0, 4 * mm)] if formsemestre.formation.is_apc(): 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] titles = [titles.get(x, "") for x in columns_ids] # 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 ) 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 ) # vertical space LINEWIDTH = 0.5 table_style = [ ( "FONTNAME", (0, 0), (-1, 0), 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)), ("VALIGN", (0, 0), (-1, -1), "TOP"), ] titles = [f"{x}" for x in titles] def _format_pv_cell(x): """convert string to paragraph""" if isinstance(x, str): return Paragraph(SU(x), cell_style) else: return x widths_by_id = { "nom": 5 * cm, "cursus": 2.8 * cm, "ects": 1.4 * cm, "devenir": 1.8 * cm, "decision_but": 1.8 * cm, } 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:]] objects.append( Table(table_cells, repeatRows=1, colWidths=widths, style=table_style) ) # Signature du directeur objects += sco_pdf.make_paras( f"""{ sco_preferences.get_preference("DirectorName", formsemestre.id) or "" }, { sco_preferences.get_preference("DirectorTitle", formsemestre.id) or "" }""", style, ) # Légende des codes codes = list(codes_cursus.CODES_EXPL.keys()) codes.sort() objects += sco_pdf.make_paras( """ Codes utilisés :""", style, ) L = [] for code in codes: L.append((code, codes_cursus.CODES_EXPL[code])) TableStyle2 = [ ( "FONTNAME", (0, 0), (-1, 0), 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)), ("LINEBEFORE", (0, 0), (0, -1), LINEWIDTH, Color(0, 0, 0)), ("LINEAFTER", (-1, 0), (-1, -1), LINEWIDTH, Color(0, 0, 0)), ] objects.append( Table( [[Paragraph(SU(x), cell_style) for x in line] for line in L], colWidths=(2 * cm, None), style=TableStyle2, ) ) return objects, a_diplome