ScoDoc/app/scodoc/sco_pv_pdf.py

361 lines
12 KiB
Python

# -*- mode: python -*-
# -*- coding: utf-8 -*-
##############################################################################
#
# Gestion scolarite IUT
#
# Copyright (c) 1999 - 2024 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
#
##############################################################################
"""Génération du PV de jury en PDF (celui en format paysage avec l'ensemble des décisions)
"""
import io
import reportlab
from reportlab.lib.units import cm, mm
from reportlab.lib.enums import TA_JUSTIFY
from reportlab.platypus import (
Paragraph,
Spacer,
PageBreak,
Table,
)
from reportlab.platypus.doctemplate import BaseDocTemplate
from reportlab.lib.pagesizes import A4, landscape
from reportlab.lib import styles
from reportlab.lib.colors import Color
from app.models import FormSemestre
from app.scodoc import codes_cursus
from app.scodoc.sco_exceptions import ScoPDFFormatError
from app.scodoc import sco_pv_dict
from app.scodoc import sco_pdf
from app.scodoc import sco_preferences
from app.scodoc.sco_pdf import SU
from app.scodoc.sco_pv_templates import PVTemplate, jury_titres
import sco_version
# ----------------------------------------------
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,
pv_title_session=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_session=pv_title_session,
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_session=pv_title_session,
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),
)
)
try:
document.build(objects)
except (ValueError, KeyError, reportlab.platypus.doctemplate.LayoutError) as exc:
raise ScoPDFFormatError(str(exc)) from exc
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,
pv_title_session=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_pv_forms
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, _ = jury_titres(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"""
<para align="center"><b>Procès-verbal de {titre_jury} du département {
sco_preferences.get_preference("DeptName", formsemestre.id) or "(sans nom)"
} - {pv_title_session} {
formsemestre.annee_scolaire()} - {
formsemestre.annee_scolaire()+1}</b></para>
""",
style,
)
objects += sco_pdf.make_paras(
f"""<para align="center"><b><i>{titre_diplome}</i></b></para>""",
style,
)
if show_title:
objects += sco_pdf.make_paras(
f"""<para align="center"><b>Semestre: {formsemestre.titre}</b></para>""",
style,
)
if sco_preferences.get_preference("PV_TITLE_WITH_VDI", formsemestre.id):
objects += sco_pdf.make_paras(
f"""<para align="center">VDI et Code: {(code_vdi or "")}</para>""", style
)
if date_jury:
objects += sco_pdf.make_paras(
f"""<para align="center">Jury tenu le {date_jury}</para>""", style
)
try:
objects += sco_pdf.make_paras(
"<para>"
+ (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,
}
+ "</para>",
style_bullet,
)
except KeyError as exc:
raise ScoPDFFormatError(msg=f"balise inconnue: {exc.args[0]}") from exc
objects += sco_pdf.make_paras(
"""<para>Le jury propose les décisions suivantes :</para>""", style
)
objects += [Spacer(0, 4 * mm)]
if formsemestre.formation.is_apc():
rows, titles = jury_but_pv.pvjury_table_but(
formsemestre,
etudids=etudids,
line_sep="<br/>",
only_diplome=only_diplome,
anonymous=anonymous,
with_paragraph_nom=with_paragraph_nom,
)
columns_ids = list(titles.keys())
a_diplome = codes_cursus.ADM in [row.get("diplome") for row in rows]
else:
dpv = sco_pv_dict.dict_pvjury(formsemestre.id, etudids=etudids, with_prev=True)
if not dpv:
return [], False
rows, titles, columns_ids = sco_pv_forms.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"<para><b>{x}</b></para>" 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"""<para spaceBefore="10mm" align="right">{
sco_preferences.get_preference("DirectorName", formsemestre.id) or ""
}, {
sco_preferences.get_preference("DirectorTitle", formsemestre.id) or ""
}</para>""",
style,
)
# Légende des codes
codes = list(codes_cursus.CODES_EXPL.keys())
codes.sort()
objects += sco_pdf.make_paras(
"""<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]))
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