ScoDoc/app/scodoc/sco_pvpdf.py

942 lines
32 KiB
Python
Raw Normal View History

2020-09-26 16:19:37 +02:00
# -*- mode: python -*-
# -*- coding: utf-8 -*-
##############################################################################
#
# Gestion scolarite IUT
#
2023-01-02 13:16:27 +01:00
# Copyright (c) 1999 - 2023 Emmanuel Viennet. All rights reserved.
2020-09-26 16:19:37 +02:00
#
# 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
"""
2021-07-13 22:07:59 +02:00
import io
2021-02-05 22:16:30 +01:00
import re
2021-07-11 23:02:35 +02:00
from PIL import Image as PILImage
from PIL import UnidentifiedImageError
2021-02-05 22:16:30 +01:00
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
2021-02-05 22:16:30 +01:00
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
2020-09-26 16:19:37 +02:00
2021-08-22 13:24:36 +02:00
from flask import g
2022-07-08 23:58:27 +02:00
from app.models import FormSemestre, Identite
2021-08-22 13:24:36 +02:00
import app.scodoc.sco_utils as scu
from app.scodoc import sco_bulletins_pdf
from app.scodoc import codes_cursus
2022-07-03 08:31:01 +02:00
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
2021-11-07 09:05:53 +01:00
from app.scodoc.sco_logos import find_logo
2022-07-07 16:24:52 +02:00
from app.scodoc.sco_cursus_dut import SituationEtudCursus
from app.scodoc.sco_pdf import SU
2022-07-03 08:31:01 +02:00
import sco_version
2020-09-26 16:19:37 +02:00
2021-02-05 22:16:30 +01:00
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
2020-09-26 16:19:37 +02:00
2021-02-05 22:16:30 +01:00
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
2020-09-26 16:19:37 +02:00
2022-07-03 08:31:01 +02:00
def page_footer(canvas, doc, logo, preferences, with_page_numbers=True):
2020-09-26 16:19:37 +02:00
"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,
)
2022-07-03 08:31:01 +02:00
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
2020-09-26 16:19:37 +02:00
2022-11-20 23:03:26 +01:00
p = sco_pdf.make_paras(
2022-07-03 08:31:01 +02:00
f"""<para>{preferences["INSTITUTION_NAME"]}</para><para>{
preferences["INSTITUTION_ADDRESS"]}</para>""",
left_foot_style,
2020-09-26 16:19:37 +02:00
)
2022-07-03 08:31:01 +02:00
np = Paragraph(f'<para fontSize="14">{doc.page}</para>', right_foot_style)
2020-09-26 16:19:37 +02:00
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)
2020-09-26 16:19:37 +02:00
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()
2022-07-03 08:31:01 +02:00
def page_header(canvas, doc, logo, preferences, only_on_first_page=False):
"Ajoute au canvas le frame avec le logo"
2020-09-26 16:19:37 +02:00
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()
2020-09-26 16:19:37 +02:00
class CourrierIndividuelTemplate(PageTemplate):
"""Template pour courrier avisant des decisions de jury (1 page par étudiant)"""
2020-09-26 16:19:37 +02:00
def __init__(
self,
document,
pagesbookmarks=None,
2020-09-26 16:19:37 +02:00
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 {}
2020-09-26 16:19:37 +02:00
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"]
2020-09-26 16:19:37 +02:00
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
2021-02-05 22:16:30 +01:00
# Search logos in dept specific dir, then in global scu.CONFIG dir
2021-11-07 09:05:53 +01:00
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,
2021-11-07 09:05:53 +01:00
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,
2021-11-07 09:05:53 +01:00
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,
)
2020-09-26 16:19:37 +02:00
def beforeDrawPage(self, canv, doc):
2020-09-26 16:19:37 +02:00
"""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))
2020-09-26 16:19:37 +02:00
if self.pdfmeta_title:
canv.setTitle(SU(self.pdfmeta_title))
2020-09-26 16:19:37 +02:00
if self.pdfmeta_subject:
canv.setSubject(SU(self.pdfmeta_subject))
2020-09-26 16:19:37 +02:00
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:
2022-07-03 08:31:01 +02:00
page_header(
canv,
2020-09-26 16:19:37 +02:00
doc,
self.logo_header,
self.preferences,
self.header_only_on_first_page,
)
if self.with_footer:
2022-07-03 08:31:01 +02:00
page_footer(
canv,
2020-09-26 16:19:37 +02:00
doc,
self.logo_footer,
self.preferences,
with_page_numbers=self.with_page_numbers,
)
class PVTemplate(CourrierIndividuelTemplate):
"""Template pour les pages des PV de jury"""
2020-09-26 16:19:37 +02:00
def __init__(
self,
document,
author=None,
title=None,
subject=None,
margins=None, # additional margins in mm (left,top,right, bottom)
2020-09-26 16:19:37 +02:00
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"],
)
2020-09-26 16:19:37 +02:00
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"]
2020-09-26 16:19:37 +02:00
def afterDrawPage(self, canv, doc):
2020-09-26 16:19:37 +02:00
"""Called after all flowables have been drawn on a page"""
pass
def beforeDrawPage(self, canv, doc):
2020-09-26 16:19:37 +02:00
"""Called before any flowables are drawn on a page"""
# If the page number is even, force a page break
CourrierIndividuelTemplate.beforeDrawPage(self, canv, doc)
2020-09-26 16:19:37 +02:00
# 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="<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>
2022-10-03 08:37:29 +02:00
Also replaces <br> by <br/>
"""
return ("</para>" + para).join(
2022-10-03 08:37:29 +02:00
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),
)
2020-09-26 16:19:37 +02:00
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.
"""
from app.scodoc import sco_pvjury
2020-09-26 16:19:37 +02:00
dpv = sco_pvjury.dict_pvjury(formsemestre_id, etudids=etudids, with_prev=True)
2020-09-26 16:19:37 +02:00
if not dpv:
return ""
# Ajoute infos sur etudiants
etuds = [x["identite"] for x in dpv["decisions"]]
sco_etud.fill_etuds_info(etuds)
2020-09-26 16:19:37 +02:00
#
2022-07-08 23:58:27 +02:00
formsemestre: FormSemestre = FormSemestre.query.get(formsemestre_id)
prefs = sco_preferences.SemPreferences(formsemestre_id)
2020-09-26 16:19:37 +02:00
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:
2022-07-08 23:58:27 +02:00
params[name] = sco_preferences.get_preference(name, formsemestre_id)
2020-09-26 16:19:37 +02:00
bookmarks = {}
objects = [] # list of PLATYPUS objects
npages = 0
2022-07-08 23:58:27 +02:00
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
2020-09-26 16:19:37 +02:00
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
2021-07-13 22:07:59 +02:00
report = io.BytesIO() # in-memory document, no disk file
2020-09-26 16:19:37 +02:00
document = BaseDocTemplate(report)
document.addPageTemplates(
CourrierIndividuelTemplate(
document,
author=f"{sco_version.SCONAME} {sco_version.SCOVERSION} (E. Viennet)",
2022-07-08 23:58:27 +02:00
title=f"Lettres décision {formsemestre.titre_annee()}",
2020-09-26 16:19:37 +02:00
subject="Décision jury",
margins=margins,
pagesbookmarks=bookmarks,
preferences=prefs,
)
)
document.build(objects)
data = report.getvalue()
return data
2022-07-08 23:58:27 +02:00
def _descr_jury(formsemestre: FormSemestre, diplome):
2020-09-26 16:19:37 +02:00
if not diplome:
2022-07-08 23:58:27 +02:00
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"
2020-09-26 16:19:37 +02:00
else:
t = "délivrance du diplôme"
s = t
return t, s # titre long, titre court
2022-07-08 23:58:27 +02:00
def pdf_lettre_individuelle(sem, decision, etud: Identite, params, signature=None):
2020-09-26 16:19:37 +02:00
"""
Renvoie une liste d'objets PLATYPUS pour intégration
dans un autre document.
"""
#
formsemestre_id = sem["formsemestre_id"]
formsemestre = FormSemestre.query.get(formsemestre_id)
2022-07-07 16:24:52 +02:00
Se: SituationEtudCursus = decision["Se"]
2022-07-08 23:58:27 +02:00
t, s = _descr_jury(
formsemestre, Se.parcours_validated() or not Se.semestre_non_terminal
)
2020-09-26 16:19:37 +02:00
objects = []
style = reportlab.lib.styles.ParagraphStyle({})
style.fontSize = 14
style.fontName = sco_preferences.get_preference("PV_FONTNAME", formsemestre_id)
2020-09-26 16:19:37 +02:00
style.leading = 18
style.alignment = TA_LEFT
2020-09-26 16:19:37 +02:00
params["semestre_id"] = formsemestre.semestre_id
2020-09-26 16:19:37 +02:00
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"] = ""
2022-06-25 07:41:37 +02:00
params["INSTITUTION_CITY"] = (
sco_preferences.get_preference("INSTITUTION_CITY", formsemestre_id) or ""
2020-09-26 16:19:37 +02:00
)
2020-09-26 16:19:37 +02:00
if decision["prev_decision_sem"]:
params["prev_semestre_id"] = decision["prev"]["semestre_id"]
params["prev_decision_sem_txt"] = ""
params["decision_orig"] = ""
2020-09-26 16:19:37 +02:00
params.update(decision["identite"])
# fix domicile
if params["domicile"]:
2022-10-03 08:37:29 +02:00
params["domicile"] = params["domicile"].replace("\\n", "<br/>")
2020-09-26 16:19:37 +02:00
# 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 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 : <b>%s</b>""" % (
2022-07-08 23:58:27 +02:00
etud.e,
s,
s,
decision["autorisations_descr"],
2020-09-26 16:19:37 +02:00
)
else:
params["autorisations_txt"] = ""
if decision["decision_sem"] and Se.parcours_validated():
params["diplome_txt"] = (
"""Vous avez donc obtenu le diplôme : <b>%(titre_formation)s</b>""" % params
)
else:
params["diplome_txt"] = ""
2022-07-08 23:58:27 +02:00
# 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)
2020-09-26 16:19:37 +02:00
# Corps de la lettre:
objects += sco_bulletins_pdf.process_field(
sco_preferences.get_preference("PV_LETTER_TEMPLATE", sem["formsemestre_id"]),
2020-09-26 16:19:37 +02:00
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 = (
2021-06-13 23:37:14 +02:00
sco_preferences.get_preference(
"PV_LETTER_PASSAGE_SIGNATURE", formsemestre_id
2021-06-13 23:37:14 +02:00
)
2022-06-25 07:41:37 +02:00
or ""
) % params
2020-09-26 16:19:37 +02:00
sig = _simulate_br(sig, '<para leftindent="%(htab1)s">')
2022-11-20 23:03:26 +01:00
objects += sco_pdf.make_paras(
2020-09-26 16:19:37 +02:00
(
"""<para leftindent="%(htab1)s" spaceBefore="25mm">"""
+ sig
+ """</para>"""
)
% params,
style,
)
else:
sig = (
2021-06-13 23:37:14 +02:00
sco_preferences.get_preference(
"PV_LETTER_DIPLOMA_SIGNATURE", formsemestre_id
2021-06-13 23:37:14 +02:00
)
2022-06-25 07:41:37 +02:00
or ""
) % params
2020-09-26 16:19:37 +02:00
sig = _simulate_br(sig, '<para leftindent="%(htab1)s">')
2022-11-20 23:03:26 +01:00
objects += sco_pdf.make_paras(
2020-09-26 16:19:37 +02:00
(
"""<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
2020-09-26 16:19:37 +02:00
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"] = ""
2020-09-26 16:19:37 +02:00
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"""
2022-07-08 23:58:27 +02:00
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"
2022-10-03 08:37:29 +02:00
] = f"""{params["decision_ue_txt"]}<br/>
<b>Niveaux de compétences:</b><br/> {decision.get("descr_decisions_rcue") or ""}
"""
2020-09-26 16:19:37 +02:00
# ----------------------------------------------
def pvjury_pdf(
dpv,
date_commission=None,
date_jury=None,
numeroArrete=None,
VDICode=None,
showTitle=False,
2020-12-01 11:03:20 +01:00
pv_title=None,
2020-09-26 16:19:37 +02:00
with_paragraph_nom=False,
anonymous=False,
):
"""Doc PDF récapitulant les décisions de jury
(tableau en format paysage)
2020-09-26 16:19:37 +02:00
dpv: result of dict_pvjury
"""
if not dpv:
return {}
sem = dpv["formsemestre"]
formsemestre_id = sem["formsemestre_id"]
objects = _pvjury_pdf_type(
dpv,
only_diplome=False,
date_commission=date_commission,
2023-02-18 18:49:52 +01:00
numero_arrete=numeroArrete,
code_vdi=VDICode,
2020-09-26 16:19:37 +02:00
date_jury=date_jury,
2023-02-18 18:49:52 +01:00
show_title=showTitle,
2020-12-01 11:03:20 +01:00
pv_title=pv_title,
2020-09-26 16:19:37 +02:00
with_paragraph_nom=with_paragraph_nom,
anonymous=anonymous,
)
jury_de_diplome = not dpv["semestre_non_terminal"]
2023-02-18 18:49:52 +01:00
# Si Jury de passage et qu'un étudiant valide le parcours
# (car il a validé antérieurement le dernier semestre)
2020-09-26 16:19:37 +02:00
# 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,
2023-02-18 18:49:52 +01:00
numero_arrete=numeroArrete,
code_vdi=VDICode,
show_title=showTitle,
2020-12-01 11:03:20 +01:00
pv_title=pv_title,
2020-09-26 16:19:37 +02:00
with_paragraph_nom=with_paragraph_nom,
anonymous=anonymous,
)
# ----- Build PDF
2021-07-13 22:07:59 +02:00
report = io.BytesIO() # in-memory document, no disk file
2020-09-26 16:19:37 +02:00
document = BaseDocTemplate(report)
document.pagesize = landscape(A4)
document.addPageTemplates(
PVTemplate(
document,
2021-08-21 17:07:44 +02:00
author="%s %s (E. Viennet)" % (sco_version.SCONAME, sco_version.SCOVERSION),
2020-09-26 16:19:37 +02:00
title=SU("PV du jury de %s" % sem["titre_num"]),
subject="PV jury",
preferences=sco_preferences.SemPreferences(formsemestre_id),
2020-09-26 16:19:37 +02:00
)
)
document.build(objects)
data = report.getvalue()
return data
2023-02-18 18:49:52 +01:00
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
2020-09-26 16:19:37 +02:00
def _pvjury_pdf_type(
dpv,
only_diplome=False,
date_commission=None,
date_jury=None,
2023-02-18 18:49:52 +01:00
numero_arrete=None,
code_vdi=None,
show_title=False,
2020-12-01 11:03:20 +01:00
pv_title=None,
2020-09-26 16:19:37 +02:00
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
"""
from app.scodoc import sco_pvjury
2023-02-18 18:49:52 +01:00
from app.but import jury_but_pv
2020-09-26 16:19:37 +02:00
# 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"]
2022-07-08 23:58:27 +02:00
formsemestre: FormSemestre = FormSemestre.query.get(formsemestre_id)
titre_jury, _ = _descr_jury(formsemestre, diplome)
2020-12-01 11:03:20 +01:00
titre_diplome = pv_title or dpv["formation"]["titre_officiel"]
2020-09-26 16:19:37 +02:00
objects = []
2023-02-18 18:49:52 +01:00
style, style_bullet = _make_pv_styles(formsemestre)
2020-09-26 16:19:37 +02:00
objects += [Spacer(0, 5 * mm)]
2022-11-20 23:03:26 +01:00
objects += sco_pdf.make_paras(
2023-02-18 18:49:52 +01:00
f"""
<para align="center"><b>Procès-verbal de {titre_jury} du département {
sco_preferences.get_preference("DeptName", formsemestre_id) or "(sans nom)"
} - Session unique {sem["anneescolaire"]}</b></para>
""",
2020-09-26 16:19:37 +02:00
style,
)
2022-11-20 23:03:26 +01:00
objects += sco_pdf.make_paras(
2023-02-18 18:49:52 +01:00
f"""<para align="center"><b><i>{titre_diplome}</i></b></para>""",
2020-09-26 16:19:37 +02:00
style,
)
2023-02-18 18:49:52 +01:00
if show_title:
2022-11-20 23:03:26 +01:00
objects += sco_pdf.make_paras(
2023-02-18 18:49:52 +01:00
f"""<para align="center"><b>Semestre: {formsemestre.titre}</b></para>""",
style,
2020-09-26 16:19:37 +02:00
)
if sco_preferences.get_preference("PV_TITLE_WITH_VDI", formsemestre_id):
2022-11-20 23:03:26 +01:00
objects += sco_pdf.make_paras(
2023-02-18 18:49:52 +01:00
f"""<para align="center">VDI et Code: {(code_vdi or "")}</para>""", style
2020-10-15 21:18:51 +02:00
)
2020-09-26 16:19:37 +02:00
if date_jury:
2022-11-20 23:03:26 +01:00
objects += sco_pdf.make_paras(
2023-02-18 18:49:52 +01:00
f"""<para align="center">Jury tenu le {date_jury}</para>""", style
2020-09-26 16:19:37 +02:00
)
2022-11-20 23:03:26 +01:00
objects += sco_pdf.make_paras(
2020-09-26 16:19:37 +02:00
"<para>"
2022-06-25 07:41:37 +02:00
+ (sco_preferences.get_preference("PV_INTRO", formsemestre_id) or "")
2020-09-26 16:19:37 +02:00
% {
2023-02-18 18:49:52 +01:00
"Decnum": numero_arrete,
"VDICode": code_vdi,
"UnivName": sco_preferences.get_preference("UnivName", formsemestre_id),
2020-09-26 16:19:37 +02:00
"Type": titre_jury,
"Date": date_commission, # deprecated
"date_commission": date_commission,
}
+ "</para>",
2023-02-18 18:49:52 +01:00
style_bullet,
2020-09-26 16:19:37 +02:00
)
2022-11-20 23:03:26 +01:00
objects += sco_pdf.make_paras(
2020-09-26 16:19:37 +02:00
"""<para>Le jury propose les décisions suivantes :</para>""", style
)
objects += [Spacer(0, 4 * mm)]
2023-02-18 18:49:52 +01:00
if formsemestre.formation.is_apc():
rows, titles = jury_but_pv.pvjury_table_but(formsemestre)
columns_ids = list(titles.keys())
else:
rows, titles, columns_ids = sco_pvjury.pvjury_table(
dpv,
only_diplome=only_diplome,
anonymous=anonymous,
with_paragraph_nom=with_paragraph_nom,
)
2020-09-26 16:19:37 +02:00
# convert to lists of tuples:
columns_ids = ["etudid"] + columns_ids
2023-02-18 18:49:52 +01:00
rows = [[line.get(x, "") for x in columns_ids] for line in rows]
2020-09-26 16:19:37 +02:00
titles = [titles.get(x, "") for x in columns_ids]
# Make a new cell style and put all cells in paragraphs
cell_style = styles.ParagraphStyle({})
2021-06-13 23:37:14 +02:00
cell_style.fontSize = sco_preferences.get_preference(
"SCOLAR_FONT_SIZE", formsemestre_id
2021-06-13 23:37:14 +02:00
)
cell_style.fontName = sco_preferences.get_preference("PV_FONTNAME", formsemestre_id)
2021-06-13 23:37:14 +02:00
cell_style.leading = 1.0 * sco_preferences.get_preference(
"SCOLAR_FONT_SIZE", formsemestre_id
2020-09-26 16:19:37 +02:00
) # vertical space
LINEWIDTH = 0.5
table_style = [
(
"FONTNAME",
(0, 0),
(-1, 0),
sco_preferences.get_preference("PV_FONTNAME", formsemestre_id),
2020-09-26 16:19:37 +02:00
),
("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"),
]
2023-02-18 18:49:52 +01:00
titles = [f"<para><b>{x}</b></para>" for x in titles]
2020-09-26 16:19:37 +02:00
def _format_pv_cell(x):
"""convert string to paragraph"""
2021-07-13 22:07:59 +02:00
if isinstance(x, str):
2020-09-26 16:19:37 +02:00
return Paragraph(SU(x), cell_style)
else:
return x
2023-02-18 18:49:52 +01:00
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:]]
# 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)
)
2020-09-26 16:19:37 +02:00
# Signature du directeur
2022-11-20 23:03:26 +01:00
objects += sco_pdf.make_paras(
2023-02-18 18:49:52 +01:00
f"""<para spaceBefore="10mm" align="right">{
sco_preferences.get_preference("DirectorName", formsemestre_id) or ""
}, {
sco_preferences.get_preference("DirectorTitle", formsemestre_id) or ""
}</para>""",
2020-09-26 16:19:37 +02:00
style,
)
# Légende des codes
codes = list(codes_cursus.CODES_EXPL.keys())
2020-09-26 16:19:37 +02:00
codes.sort()
2022-11-20 23:03:26 +01:00
objects += sco_pdf.make_paras(
2020-09-26 16:19:37 +02:00
"""<para spaceBefore="15mm" fontSize="14">
<b>Codes utilisés :</b></para>""",
style,
)
L = []
for code in codes:
L.append((code, codes_cursus.CODES_EXPL[code]))
2020-09-26 16:19:37 +02:00
TableStyle2 = [
(
"FONTNAME",
(0, 0),
(-1, 0),
sco_preferences.get_preference("PV_FONTNAME", formsemestre_id),
2020-09-26 16:19:37 +02:00
),
("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