forked from ScoDoc/ScoDoc
389 lines
13 KiB
Python
389 lines
13 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, 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,
|
|
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,
|
|
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,
|
|
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,
|
|
)
|
|
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(
|
|
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
|