ScoDoc/app/scodoc/sco_pv_pdf.py

389 lines
13 KiB
Python
Raw Normal View History

# -*- mode: python -*-
# -*- coding: utf-8 -*-
##############################################################################
#
# Gestion scolarite IUT
#
2023-12-31 23:04:06 +01:00
# 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, TA_LEFT, TA_RIGHT
from reportlab.platypus import Paragraph, Spacer, PageBreak, Table, TableStyle
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,
2024-09-16 14:18:32 +02:00
pv_subtitle=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,
2024-09-16 14:18:32 +02:00
pv_subtitle=pv_subtitle,
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,
2024-09-16 14:18:32 +02:00
pv_subtitle=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'obj platypus, et un booléen 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,
)
2024-09-16 14:18:32 +02:00
if pv_subtitle:
objects += sco_pdf.make_paras(
f"""<para align="center"><b><i>{pv_subtitle}</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(
2024-09-16 14:18:32 +02:00
f"""<para align="center">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)
)
# Table sous la table avec signatures
# Create paragraphs
left_para = sco_pdf.make_paras(
f"""<para align="left">{
sco_preferences.get_preference("PV_SIGNATURE_LEFT", formsemestre.id) or ""
}</para>""",
style,
)
# Signature du directeur
right_para = 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,
)
# Create a table with two cells
data = [[left_para, right_para]]
# Create the table
table_signatures = Table(data)
# Apply table style to remove borders
table_signatures.setStyle(
TableStyle(
[
("BOX", (0, 0), (-1, -1), 0, "white"), # No border for the table
("INNERGRID", (0, 0), (-1, -1), 0, "white"), # No inner grid lines
("VALIGN", (0, 0), (-1, -1), "TOP"), # Align content to the top
]
)
)
objects.append(Spacer(0, 6 * mm))
objects.append(table_signatures)
# 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