forked from ScoDoc/ScoDoc
926 lines
31 KiB
Python
926 lines
31 KiB
Python
# -*- mode: python -*-
|
|
# -*- coding: utf-8 -*-
|
|
|
|
##############################################################################
|
|
#
|
|
# Gestion scolarite IUT
|
|
#
|
|
# Copyright (c) 1999 - 2022 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 sco_codes_parcours
|
|
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.makeParas(
|
|
f"""<para>{preferences["INSTITUTION_NAME"]}</para><para>{
|
|
preferences["INSTITUTION_ADDRESS"]}</para>""",
|
|
left_foot_style,
|
|
)
|
|
|
|
np = Paragraph(f'<para fontSize="14">{doc.page}</para>', 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="<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_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
|
|
|
|
dpv = sco_pvjury.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", "<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 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>""" % (
|
|
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 : <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 Se.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.makeParas(
|
|
(
|
|
"""<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.makeParas(
|
|
(
|
|
"""<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 ""}
|
|
"""
|
|
|
|
|
|
# ----------------------------------------------
|
|
def pvjury_pdf(
|
|
dpv,
|
|
date_commission=None,
|
|
date_jury=None,
|
|
numeroArrete=None,
|
|
VDICode=None,
|
|
showTitle=False,
|
|
pv_title=None,
|
|
with_paragraph_nom=False,
|
|
anonymous=False,
|
|
):
|
|
"""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,
|
|
only_diplome=False,
|
|
date_commission=date_commission,
|
|
numeroArrete=numeroArrete,
|
|
VDICode=VDICode,
|
|
date_jury=date_jury,
|
|
showTitle=showTitle,
|
|
pv_title=pv_title,
|
|
with_paragraph_nom=with_paragraph_nom,
|
|
anonymous=anonymous,
|
|
)
|
|
|
|
jury_de_diplome = not dpv["semestre_non_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,
|
|
numeroArrete=numeroArrete,
|
|
VDICode=VDICode,
|
|
showTitle=showTitle,
|
|
pv_title=pv_title,
|
|
with_paragraph_nom=with_paragraph_nom,
|
|
anonymous=anonymous,
|
|
)
|
|
|
|
# ----- Build PDF
|
|
report = io.BytesIO() # in-memory document, no disk file
|
|
document = BaseDocTemplate(report)
|
|
document.pagesize = landscape(A4)
|
|
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"]),
|
|
subject="PV jury",
|
|
preferences=sco_preferences.SemPreferences(formsemestre_id),
|
|
)
|
|
)
|
|
|
|
document.build(objects)
|
|
data = report.getvalue()
|
|
return data
|
|
|
|
|
|
def _pvjury_pdf_type(
|
|
dpv,
|
|
only_diplome=False,
|
|
date_commission=None,
|
|
date_jury=None,
|
|
numeroArrete=None,
|
|
VDICode=None,
|
|
showTitle=False,
|
|
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
|
|
"""
|
|
from app.scodoc import sco_pvjury
|
|
|
|
# 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)
|
|
titre_jury, _ = _descr_jury(formsemestre, diplome)
|
|
titre_diplome = pv_title or dpv["formation"]["titre_officiel"]
|
|
objects = []
|
|
|
|
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
|
|
bulletStyle = reportlab.lib.styles.ParagraphStyle({})
|
|
bulletStyle.fontSize = 12
|
|
bulletStyle.fontName = sco_preferences.get_preference(
|
|
"PV_FONTNAME", formsemestre_id
|
|
)
|
|
bulletStyle.leading = 12
|
|
bulletStyle.alignment = TA_JUSTIFY
|
|
bulletStyle.firstLineIndent = 0
|
|
bulletStyle.leftIndent = indent
|
|
bulletStyle.bulletIndent = indent
|
|
bulletStyle.bulletFontName = "Times-Roman"
|
|
bulletStyle.bulletFontSize = 11
|
|
bulletStyle.spaceBefore = 5 * mm
|
|
bulletStyle.spaceAfter = 5 * mm
|
|
|
|
objects += [Spacer(0, 5 * mm)]
|
|
objects += sco_pdf.makeParas(
|
|
"""
|
|
<para align="center"><b>Procès-verbal de %s du département %s - Session unique %s</b></para>
|
|
"""
|
|
% (
|
|
titre_jury,
|
|
sco_preferences.get_preference("DeptName", formsemestre_id) or "(sans nom)",
|
|
sem["anneescolaire"],
|
|
),
|
|
style,
|
|
)
|
|
|
|
objects += sco_pdf.makeParas(
|
|
"""
|
|
<para align="center"><b><i>%s</i></b></para>
|
|
"""
|
|
% titre_diplome,
|
|
style,
|
|
)
|
|
|
|
if showTitle:
|
|
objects += sco_pdf.makeParas(
|
|
"""<para align="center"><b>Semestre: %s</b></para>""" % sem["titre"], style
|
|
)
|
|
if sco_preferences.get_preference("PV_TITLE_WITH_VDI", formsemestre_id):
|
|
objects += sco_pdf.makeParas(
|
|
"""<para align="center">VDI et Code: %s</para>""" % (VDICode or ""), style
|
|
)
|
|
|
|
if date_jury:
|
|
objects += sco_pdf.makeParas(
|
|
"""<para align="center">Jury tenu le %s</para>""" % date_jury, style
|
|
)
|
|
|
|
objects += sco_pdf.makeParas(
|
|
"<para>"
|
|
+ (sco_preferences.get_preference("PV_INTRO", formsemestre_id) or "")
|
|
% {
|
|
"Decnum": numeroArrete,
|
|
"VDICode": VDICode,
|
|
"UnivName": sco_preferences.get_preference("UnivName", formsemestre_id),
|
|
"Type": titre_jury,
|
|
"Date": date_commission, # deprecated
|
|
"date_commission": date_commission,
|
|
}
|
|
+ "</para>",
|
|
bulletStyle,
|
|
)
|
|
|
|
objects += sco_pdf.makeParas(
|
|
"""<para>Le jury propose les décisions suivantes :</para>""", style
|
|
)
|
|
objects += [Spacer(0, 4 * mm)]
|
|
lines, titles, columns_ids = sco_pvjury.pvjury_table(
|
|
dpv,
|
|
only_diplome=only_diplome,
|
|
anonymous=anonymous,
|
|
with_paragraph_nom=with_paragraph_nom,
|
|
)
|
|
# convert to lists of tuples:
|
|
columns_ids = ["etudid"] + columns_ids
|
|
lines = [[line.get(x, "") for x in columns_ids] for line in lines]
|
|
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 = ["<para><b>%s</b></para>" % 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
|
|
|
|
Pt = [[_format_pv_cell(x) for x in line[1:]] for line in ([titles] + lines)]
|
|
widths = [6 * cm, 2.8 * cm, 2.8 * cm, None, None, None, None]
|
|
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(Pt, repeatRows=1, colWidths=widths, style=table_style))
|
|
|
|
# Signature du directeur
|
|
objects += sco_pdf.makeParas(
|
|
"""<para spaceBefore="10mm" align="right">
|
|
%s, %s</para>"""
|
|
% (
|
|
sco_preferences.get_preference("DirectorName", formsemestre_id) or "",
|
|
sco_preferences.get_preference("DirectorTitle", formsemestre_id) or "",
|
|
),
|
|
style,
|
|
)
|
|
|
|
# Légende des codes
|
|
codes = list(sco_codes_parcours.CODES_EXPL.keys())
|
|
codes.sort()
|
|
objects += sco_pdf.makeParas(
|
|
"""<para spaceBefore="15mm" fontSize="14">
|
|
<b>Codes utilisés :</b></para>""",
|
|
style,
|
|
)
|
|
L = []
|
|
for code in codes:
|
|
L.append((code, sco_codes_parcours.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
|