2023-02-19 15:45:27 +01:00
|
|
|
# -*- 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_etud
|
|
|
|
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 ""
|
|
|
|
# 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 _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.query.get(formsemestre_id)
|
2023-02-19 19:26:57 +01:00
|
|
|
situation_etud: SituationEtudCursus = decision["Se"]
|
|
|
|
titre_jury, titre_jury_court = jury_titres(
|
|
|
|
formsemestre,
|
|
|
|
situation_etud.parcours_validated() or not situation_etud.semestre_non_terminal,
|
2023-02-19 15:45:27 +01:00
|
|
|
)
|
|
|
|
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"]
|
2023-02-19 19:26:57 +01:00
|
|
|
params["type_jury"] = titre_jury # type de jury (passage ou delivrance)
|
|
|
|
params["type_jury_abbrv"] = titre_jury_court # idem, abbrégé
|
2023-02-19 15:45:27 +01:00
|
|
|
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", "<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
|
2023-02-19 19:26:57 +01:00
|
|
|
if decision["autorisations"] and not situation_etud.parcours_validated():
|
2023-02-19 15:45:27 +01:00
|
|
|
if len(decision["autorisations"]) > 1:
|
2023-02-19 19:26:57 +01:00
|
|
|
titre_jury_court = "s"
|
2023-02-19 15:45:27 +01:00
|
|
|
else:
|
2023-02-19 19:26:57 +01:00
|
|
|
titre_jury_court = ""
|
2023-02-19 15:45:27 +01:00
|
|
|
params[
|
|
|
|
"autorisations_txt"
|
|
|
|
] = """Vous êtes autorisé%s à continuer dans le%s semestre%s : <b>%s</b>""" % (
|
|
|
|
etud.e,
|
2023-02-19 19:26:57 +01:00
|
|
|
titre_jury_court,
|
|
|
|
titre_jury_court,
|
2023-02-19 15:45:27 +01:00
|
|
|
decision["autorisations_descr"],
|
|
|
|
)
|
|
|
|
else:
|
|
|
|
params["autorisations_txt"] = ""
|
|
|
|
|
2023-02-19 19:26:57 +01:00
|
|
|
if decision["decision_sem"] and situation_etud.parcours_validated():
|
2023-02-19 15:45:27 +01:00
|
|
|
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.
|
2023-02-19 19:26:57 +01:00
|
|
|
if situation_etud.semestre_non_terminal:
|
2023-02-19 15:45:27 +01:00
|
|
|
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 ""}
|
|
|
|
"""
|