Bul. BUT Bul: bookmarks, reportlab templates

This commit is contained in:
Emmanuel Viennet 2022-03-12 09:40:48 +01:00
parent dcc06d6169
commit 55c4f2c799
8 changed files with 143 additions and 67 deletions

View File

@ -24,6 +24,7 @@ class BulletinGeneratorStandardBUT(BulletinGeneratorStandard):
list_in_menu = False # spécialisation du BulletinGeneratorStandard, ne pas présenter à l'utilisateur
scale_table_in_page = False
small_fontsize = "8"
def bul_table(self, format="html"):
"""Génère la table centrale du bulletin de notes
@ -77,16 +78,28 @@ class BulletinGeneratorStandardBUT(BulletinGeneratorStandard):
"coef": 2 * cm,
}
title_bg = tuple(x / 255.0 for x in title_bg)
nota_bene = "La moyenne des ressources et SAÉs dans une UE dépend des poids donnés aux évaluations."
# elems pour générer table avec gen_table (liste de dicts)
rows = [
# Ligne de titres
{
"titre": "Unités d'enseignement",
"moyenne": "Note/20",
"moyenne": Paragraph("<para align=right><b>Note/20</b></para>"),
"coef": "Coef.",
"_coef_pdf": Paragraph("<para align=right><b><i>Coef.</i></b></para>"),
"_css_row_class": "note_bold",
"_pdf_row_markup": ["b"],
"_pdf_style": [
("BACKGROUND", (0, 0), (-1, 0), title_bg),
# ("BOTTOMPADDING", (0, 0), (-1, 0), 7),
],
},
{
"titre": nota_bene,
"_titre_pdf": Paragraph(
f"<para fontSize={self.small_fontsize}><i>{nota_bene}</i></para>"
),
"_titre_colspan": 3,
"_pdf_style": [
("BACKGROUND", (0, 0), (-1, 0), title_bg),
("BOTTOMPADDING", (0, 0), (-1, 0), 7),
@ -98,7 +111,7 @@ class BulletinGeneratorStandardBUT(BulletinGeneratorStandard):
blue,
),
],
}
},
]
col_keys = ["titre", "coef", "moyenne"] # noms des colonnes à afficher
for ue_acronym, ue in self.infos["ues"].items():
@ -106,7 +119,9 @@ class BulletinGeneratorStandardBUT(BulletinGeneratorStandard):
moy_ue = ue.get("moyenne")
t = {
"titre": f"{ue_acronym} - {ue['titre']}",
"moyenne": moy_ue.get("value", "-") if moy_ue is not None else "-",
"moyenne": Paragraph(
f"""<para align=right><b>{moy_ue.get("value", "-") if moy_ue is not None else "-"}</b></para>"""
),
"_css_row_class": "note_bold",
"_pdf_row_markup": ["b"],
"_pdf_style": [
@ -128,7 +143,7 @@ class BulletinGeneratorStandardBUT(BulletinGeneratorStandard):
"titre": f"""Bonus: {ue['bonus']} - Malus: {
ue["malus"]}""",
"coef": ects_txt,
"_coef_pdf": Paragraph(f"""<para align=right>{ects_txt}</para>"""),
"_coef_pdf": Paragraph(f"""<para align=left>{ects_txt}</para>"""),
"_coef_colspan": 2,
# "_css_row_class": "",
# "_pdf_row_markup": [""],
@ -157,7 +172,9 @@ class BulletinGeneratorStandardBUT(BulletinGeneratorStandard):
for mod_code, mod in ue[mod_type].items():
t = {
"titre": f"{mod_code} {self.infos[mod_type][mod_code]['titre']}",
"moyenne": mod["moyenne"],
"moyenne": Paragraph(
f'<para align=right>{mod["moyenne"]}</para>'
),
"coef": mod["coef"],
"_coef_pdf": Paragraph(
f"<para align=right><i>{mod['coef']}</i></para>"
@ -203,7 +220,6 @@ class BulletinGeneratorStandardBUT(BulletinGeneratorStandard):
- pdf_style : commandes table Platypus
- largeurs de colonnes pour PDF
"""
poids_fontsize = "8"
# UE à utiliser pour les poids (# colonne/UE)
ue_acros = list(self.infos["ues"].keys()) # ['RT1.1', 'RT2.1', 'RT3.1']
# Colonnes à afficher:
@ -243,7 +259,7 @@ class BulletinGeneratorStandardBUT(BulletinGeneratorStandard):
}
for ue_acro in ue_acros:
t[ue_acro] = Paragraph(
f"<para align=right fontSize={poids_fontsize}><i>{ue_acro}</i></para>"
f"<para align=right fontSize={self.small_fontsize}><i>{ue_acro}</i></para>"
)
rows = [t]
for mod_code, mod in self.infos[mod_type].items():
@ -273,7 +289,7 @@ class BulletinGeneratorStandardBUT(BulletinGeneratorStandard):
"moyenne": e["note"]["value"],
"coef": e["coef"],
"_coef_pdf": Paragraph(
f"<para align=right fontSize={poids_fontsize}><i>{e['coef']}</i></para>"
f"<para align=right fontSize={self.small_fontsize}><i>{e['coef']}</i></para>"
),
"_pdf_style": [
(
@ -288,7 +304,7 @@ class BulletinGeneratorStandardBUT(BulletinGeneratorStandard):
col_idx = 1 # 1ere col. poids
for ue_acro in ue_acros:
t[ue_acro] = Paragraph(
f"""<para align=right fontSize={poids_fontsize}><i>{e["poids"].get(ue_acro, "")}</i></para>"""
f"""<para align=right fontSize={self.small_fontsize}><i>{e["poids"].get(ue_acro, "") or ""}</i></para>"""
)
t["_pdf_style"].append(
(

View File

@ -761,7 +761,7 @@ if __name__ == "__main__":
doc = io.BytesIO()
document = sco_pdf.BaseDocTemplate(doc)
document.addPageTemplates(
sco_pdf.ScolarsPageTemplate(
sco_pdf.ScoDocPageTemplate(
document,
)
)

View File

@ -154,9 +154,11 @@ class BulletinGenerator:
from app.scodoc import sco_preferences
formsemestre_id = self.infos["formsemestre_id"]
objects = []
# partie haute du bulletin
objects = self.bul_title_pdf() # pylint: disable=no-member
objects += self.bul_title_pdf() # pylint: disable=no-member
objects += [sco_pdf.DebutBulletin(self.infos["etud"]["nomprenom"])]
# table des notes
objects += self.bul_table(format="pdf") # pylint: disable=no-member
# infos sous la table
@ -168,6 +170,7 @@ class BulletinGenerator:
# Réduit sur une page
objects = [KeepInFrame(0, 0, objects, mode="shrink")]
#
# objects.append(sco_pdf.FinBulletin())
if not stand_alone:
objects.append(PageBreak()) # insert page break at end
return objects
@ -177,7 +180,7 @@ class BulletinGenerator:
report = io.BytesIO() # in-memory document, no disk file
document = sco_pdf.BaseDocTemplate(report)
document.addPageTemplates(
sco_pdf.ScolarsPageTemplate(
sco_pdf.ScoDocPageTemplate(
document,
author="%s %s (E. Viennet) [%s]"
% (sco_version.SCONAME, sco_version.SCOVERSION, self.description),

View File

@ -54,9 +54,7 @@ import io
import re
import time
import traceback
from pydoc import html
from reportlab.platypus.doctemplate import BaseDocTemplate
import pydoc
from flask import g, request
@ -74,17 +72,17 @@ import app.scodoc.sco_utils as scu
import sco_version
def pdfassemblebulletins(
formsemestre_id,
objects,
bul_title,
def assemble_bulletins_pdf(
formsemestre_id: int,
story: list,
bul_title: str,
infos,
pagesbookmarks,
filigranne=None,
server_name="",
):
"generate PDF document from a list of PLATYPUS objects"
if not objects:
"Generate PDF document from a story (list of PLATYPUS objects)."
if not story:
return ""
# Paramètres de mise en page
margins = (
@ -93,11 +91,10 @@ def pdfassemblebulletins(
sco_preferences.get_preference("right_margin", formsemestre_id),
sco_preferences.get_preference("bottom_margin", formsemestre_id),
)
report = io.BytesIO() # in-memory document, no disk file
document = BaseDocTemplate(report)
document = sco_pdf.BulletinDocTemplate(report)
document.addPageTemplates(
sco_pdf.ScolarsPageTemplate(
sco_pdf.ScoDocPageTemplate(
document,
author="%s %s (E. Viennet)" % (sco_version.SCONAME, sco_version.SCOVERSION),
title="Bulletin %s" % bul_title,
@ -109,7 +106,8 @@ def pdfassemblebulletins(
preferences=sco_preferences.SemPreferences(formsemestre_id),
)
)
document.build(objects)
document.build(story)
# document.multiBuild(story)
data = report.getvalue()
return data
@ -121,7 +119,8 @@ def replacement_function(match):
if logo is not None:
return r'<img %s src="%s"%s/>' % (match.group(2), logo.filepath, match.group(4))
raise ScoValueError(
'balise "%s": logo "%s" introuvable' % (html.escape(balise), html.escape(name))
'balise "%s": logo "%s" introuvable'
% (pydoc.html.escape(balise), pydoc.html.escape(name))
)
@ -205,7 +204,7 @@ def get_formsemestre_bulletins_pdf(formsemestre_id, version="selectedevals"):
server_name = ""
try:
sco_pdf.PDFLOCK.acquire()
pdfdoc = pdfassemblebulletins(
pdfdoc = assemble_bulletins_pdf(
formsemestre_id,
fragments,
formsemestre.titre_mois(),
@ -255,7 +254,7 @@ def get_etud_bulletins_pdf(etudid, version="selectedevals"):
server_name = ""
try:
sco_pdf.PDFLOCK.acquire()
pdfdoc = pdfassemblebulletins(
pdfdoc = assemble_bulletins_pdf(
None,
fragments,
etud["nomprenom"],

View File

@ -73,7 +73,7 @@ class BulletinGeneratorStandard(sco_bulletins_generator.BulletinGenerator):
description = "standard ScoDoc (version 2011)" # la description doit être courte: elle apparait dans le menu de paramètrage ScoDoc
supported_formats = ["html", "pdf"]
def bul_title_pdf(self):
def bul_title_pdf(self) -> list:
"""Génère la partie "titre" du bulletin de notes.
Renvoie une liste d'objets platypus
"""
@ -115,11 +115,11 @@ class BulletinGeneratorStandard(sco_bulletins_generator.BulletinGenerator):
- en PDF: une liste d'objets platypus
"""
H = [] # html
Op = [] # objets platypus
story = [] # objets platypus
# ----- ABSENCES
if self.preferences["bul_show_abs"]:
nbabs = self.infos["nbabs"]
Op.append(Spacer(1, 2 * mm))
story.append(Spacer(1, 2 * mm))
if nbabs:
H.append(
"""<p class="bul_abs">
@ -130,7 +130,7 @@ class BulletinGeneratorStandard(sco_bulletins_generator.BulletinGenerator):
"""
% self.infos
)
Op.append(
story.append(
Paragraph(
SU(
"%(nbabs)s absences (1/2 journées), dont %(nbabsjust)s justifiées."
@ -141,7 +141,7 @@ class BulletinGeneratorStandard(sco_bulletins_generator.BulletinGenerator):
)
else:
H.append("""<p class="bul_abs">Pas d'absences signalées.</p>""")
Op.append(Paragraph(SU("Pas d'absences signalées."), self.CellStyle))
story.append(Paragraph(SU("Pas d'absences signalées."), self.CellStyle))
# ---- APPRECIATIONS
# le dir. des etud peut ajouter des appreciations,
@ -168,10 +168,10 @@ class BulletinGeneratorStandard(sco_bulletins_generator.BulletinGenerator):
% self.infos
)
H.append("</div>")
# Appreciations sur PDF:
# Appréciations sur PDF:
if self.infos.get("appreciations_list", False):
Op.append(Spacer(1, 3 * mm))
Op.append(
story.append(Spacer(1, 3 * mm))
story.append(
Paragraph(
SU("Appréciation : " + "\n".join(self.infos["appreciations_txt"])),
self.CellStyle,
@ -180,7 +180,7 @@ class BulletinGeneratorStandard(sco_bulletins_generator.BulletinGenerator):
# ----- DECISION JURY
if self.preferences["bul_show_decision"]:
Op += sco_bulletins_pdf.process_field(
story += sco_bulletins_pdf.process_field(
self.preferences["bul_pdf_caption"],
self.infos,
self.FieldStyle,
@ -196,7 +196,12 @@ class BulletinGeneratorStandard(sco_bulletins_generator.BulletinGenerator):
# -----
if format == "pdf":
return [KeepTogether(Op)]
if self.scale_table_in_page:
# le scaling (pour tenir sur une page) semble incompatible avec
# le KeepTogether()
return story
else:
return [KeepTogether(story)]
elif format == "html":
return "\n".join(H)

View File

@ -175,7 +175,27 @@ def bold_paras(L, tag="b", close=None):
return [b + (x or "") + close for x in L]
class ScolarsPageTemplate(PageTemplate):
class BulMarker(Flowable):
"""Custom Flowables pour nos bulletins PDF: invisibles, juste pour se repérer"""
def wrap(self, *args):
return (0, 0)
def draw(self):
return
class DebutBulletin(BulMarker):
"""Début d'un bulletin.
Element vide utilisé pour générer les bookmarks
"""
def __init__(self, bookmark=None):
self.bookmark = bookmark
super().__init__()
class ScoDocPageTemplate(PageTemplate):
"""Our own page template."""
def __init__(
@ -203,6 +223,8 @@ class ScolarsPageTemplate(PageTemplate):
self.pdfmeta_subject = subject
self.server_name = server_name
self.filigranne = filigranne
self.page_number = 1
self.current_page_bookmark = None
self.footer_template = footer_template
if self.preferences:
self.with_page_background = self.preferences["bul_pdf_with_background"]
@ -217,7 +239,7 @@ class ScolarsPageTemplate(PageTemplate):
document.pagesize[0] - 20.0 * mm - left * mm - right * mm,
document.pagesize[1] - 18.0 * mm - top * mm - bottom * mm,
)
PageTemplate.__init__(self, "ScolarsPageTemplate", [content])
super().__init__("ScoDocPageTemplate", [content])
self.logo = None
logo = find_logo(
logoname="bul_pdf_background", dept_id=g.scodoc_dept_id
@ -265,21 +287,6 @@ class ScolarsPageTemplate(PageTemplate):
) = self.logo
canvas.drawImage(image, inch, doc.pagesize[1] - inch, width, height)
# ---- Filigranne (texte en diagonal en haut a gauche de chaque page)
if self.filigranne:
if isinstance(self.filigranne, str):
filigranne = self.filigranne # same for all pages
else:
filigranne = self.filigranne.get(doc.page, None)
if filigranne:
canvas.saveState()
canvas.translate(9 * cm, 27.6 * cm)
canvas.rotate(30)
canvas.scale(4.5, 4.5)
canvas.setFillColorRGB(1.0, 0.65, 0.65)
canvas.drawRightString(0, 0, SU(filigranne))
canvas.restoreState()
# ---- Add some meta data and bookmarks
if self.pdfmeta_author:
canvas.setAuthor(SU(self.pdfmeta_author))
@ -287,12 +294,16 @@ class ScolarsPageTemplate(PageTemplate):
canvas.setTitle(SU(self.pdfmeta_title))
if self.pdfmeta_subject:
canvas.setSubject(SU(self.pdfmeta_subject))
bm = self.pagesbookmarks.get(doc.page, None)
if bm != None:
key = bm
txt = SU(bm)
canvas.bookmarkPage(key)
canvas.addOutlineEntry(txt, bm)
# if self.current_page_bookmark:
# canvas.bookmarkPage(self.current_page_bookmark)
# canvas.addOutlineEntry(
# SU(self.current_page_bookmark), self.current_page_bookmark
# )
# else: XXX A REMETTRE
# bookmark = self.pagesbookmarks.get(doc.page, None)
# if bookmark != None:
# canvas.bookmarkPage(bookmark)
# canvas.addOutlineEntry(SU(bookmark), bookmark)
# ---- Footer
canvas.setFont(
self.preferences["SCOLAR_FONT"], self.preferences["SCOLAR_FONT_SIZE_FOOT"]
@ -308,6 +319,48 @@ class ScolarsPageTemplate(PageTemplate):
)
canvas.restoreState()
def afterDrawPage(self, canvas, doc):
if not self.preferences:
return
# ---- Filigranne (texte en diagonal en haut a gauche de chaque page)
if self.filigranne:
if isinstance(self.filigranne, str):
filigranne = self.filigranne # same for all pages
else:
filigranne = self.filigranne.get(doc.page, None)
if filigranne:
canvas.saveState()
canvas.translate(9 * cm, 27.6 * cm)
canvas.rotate(30)
canvas.scale(4.5, 4.5)
canvas.setFillColorRGB(1.0, 0.65, 0.65, alpha=0.6)
canvas.drawRightString(0, 0, SU(filigranne))
canvas.restoreState()
def afterPage(self):
"""Called after all flowables have been drawn on a page.
Increment pageNum since the page has been completed.
"""
self.page_number += 1
self.current_page_bookmark = None
class BulletinDocTemplate(BaseDocTemplate):
"""Doc template pour les bulletins PDF
ajoute la gestion des bookmarks
"""
# inspired by https://www.reportlab.com/snippets/13/
def afterFlowable(self, flowable):
"""Called by Reportlab after each flowable"""
if isinstance(flowable, DebutBulletin):
if flowable.bookmark:
self.canv.bookmarkPage(flowable.bookmark)
self.canv.addOutlineEntry(
SU(flowable.bookmark), flowable.bookmark, level=0, closed=None
)
# log(f"afterFlowable addOutlineEntry {flowable.bookmark} page {self.page}")
def _makeTimeDict():
# ... suboptimal but we don't care
@ -333,7 +386,7 @@ def pdf_basic_page(
report = io.BytesIO() # in-memory document, no disk file
document = BaseDocTemplate(report)
document.addPageTemplates(
ScolarsPageTemplate(
ScoDocPageTemplate(
document,
title=title,
author="%s %s (E. Viennet)" % (sco_version.SCONAME, sco_version.SCOVERSION),

View File

@ -378,7 +378,7 @@ def _trombino_pdf(groups_infos):
# Build document
document = BaseDocTemplate(report)
document.addPageTemplates(
sco_pdf.ScolarsPageTemplate(
sco_pdf.ScoDocPageTemplate(
document,
preferences=sco_preferences.SemPreferences(sem["formsemestre_id"]),
)
@ -458,7 +458,7 @@ def _listeappel_photos_pdf(groups_infos):
# Build document
document = BaseDocTemplate(report)
document.addPageTemplates(
sco_pdf.ScolarsPageTemplate(
sco_pdf.ScoDocPageTemplate(
document,
preferences=sco_preferences.SemPreferences(sem["formsemestre_id"]),
)

View File

@ -264,7 +264,7 @@ def pdf_trombino_tours(
filename = "trombino-%s-%s.pdf" % (DeptName, groups_infos.groups_filename)
document = BaseDocTemplate(report)
document.addPageTemplates(
ScolarsPageTemplate(
ScoDocPageTemplate(
document,
preferences=sco_preferences.SemPreferences(),
)
@ -460,7 +460,7 @@ def pdf_feuille_releve_absences(
else:
document = BaseDocTemplate(report, pagesize=taille)
document.addPageTemplates(
ScolarsPageTemplate(
ScoDocPageTemplate(
document,
preferences=sco_preferences.SemPreferences(),
)