# -*- 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 lettres individuelles de jury """ # code initialement dans sco_pvpdf.py 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 from reportlab.platypus import PageBreak, Table, Image from reportlab.platypus.doctemplate import BaseDocTemplate from reportlab.lib import styles from app.models import FormSemestre, Identite import app.scodoc.sco_utils as scu from app.scodoc import sco_bulletins_pdf from app.scodoc import sco_pv_dict from app.scodoc import sco_pdf from app.scodoc import sco_preferences from app.scodoc.sco_exceptions import ScoValueError from app.scodoc.sco_cursus_dut import SituationEtudCursus from app.scodoc.sco_pv_templates import CourrierIndividuelTemplate, jury_titres import sco_version 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_pv_dict.dict_pvjury(formsemestre_id, etudids=etudids, with_prev=True) if not dpv: return "" # 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") or decision.get("decisions_ue") ): # decision prise etud = Identite.get_etud(decision["identite"]["etudid"]) params["nomEtud"] = etud.nomprenom # backward compat 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 _simulate_br(paragraph_txt: str, para="<para>") -> str: """Reportlab bug turnaround (could be removed in a future version). p is a string with Reportlab intra-paragraph XML tags. Replaces <br> (currently ignored by Reportlab) by </para><para> Also replaces <br> by <br/> """ return ("</para>" + para).join( re.split(r"<.*?br.*?/>", paragraph_txt.replace("<br>", "<br/>")) ) 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_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.get_formsemestre(formsemestre_id) situation_etud: SituationEtudCursus = decision["Se"] titre_jury, titre_jury_court = jury_titres( formsemestre, situation_etud.parcours_validated() or not situation_etud.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"] = titre_jury # type de jury (passage ou delivrance) params["type_jury_abbrv"] = titre_jury_court # 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.get("domicile"): params["domicile"] = params["domicile"].replace("\\n", "<br/>") # UE capitalisées: if decision["decisions_ue"] and decision["decisions_ue_descr"]: params["decision_ue_txt"] = ( """<b>Unité%(decisions_ue_descr_plural)s d'Enseignement %(decision_orig)s capitalisée%(decisions_ue_descr_plural)s : %(decisions_ue_descr)s</b>""" % params ) else: params["decision_ue_txt"] = "" # Mention params["mention"] = decision["mention"] # Informations sur compensations if decision["observation"]: params["observation_txt"] = ( """<b>Observation :</b> %(observation)s.""" % decision ) else: params["observation_txt"] = "" # Autorisations de passage if decision["autorisations"] and not situation_etud.parcours_validated(): if len(decision["autorisations"]) > 1: titre_jury_court = "s" else: titre_jury_court = "" params[ "autorisations_txt" ] = """Vous êtes autorisé%s à continuer dans le%s semestre%s : <b>%s</b>""" % ( etud.e, titre_jury_court, titre_jury_court, decision["autorisations_descr"], ) else: params["autorisations_txt"] = "" if decision["decision_sem"] and situation_etud.parcours_validated(): params["diplome_txt"] = ( """Vous avez donc obtenu le diplôme : <b>%(titre_formation)s</b>""" % 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 situation_etud.semestre_non_terminal: sig = ( sco_preferences.get_preference( "PV_LETTER_PASSAGE_SIGNATURE", formsemestre_id ) or "" ) % params sig = _simulate_br(sig, '<para leftindent="%(htab1)s">') objects += sco_pdf.make_paras( ( """<para leftindent="%(htab1)s" spaceBefore="25mm">""" + sig + """</para>""" ) % params, style, ) else: sig = ( sco_preferences.get_preference( "PV_LETTER_DIPLOMA_SIGNATURE", formsemestre_id ) or "" ) % params sig = _simulate_br(sig, '<para leftindent="%(htab1)s">') objects += sco_pdf.make_paras( ( """<para leftindent="%(htab1)s" spaceBefore="25mm">""" + sig + """</para>""" ) % 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"""<b>Décision du semestre antérieur S{params['prev_semestre_id']} :</b> { 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"]}<br/> <b>Niveaux de compétences:</b><br/> {decision.get("descr_decisions_rcue") or ""} """