forked from ScoDoc/ScoDoc
345 lines
12 KiB
Python
345 lines
12 KiB
Python
|
# -*- mode: python -*-
|
||
|
# -*- coding: utf-8 -*-
|
||
|
|
||
|
##############################################################################
|
||
|
#
|
||
|
# Gestion scolarite IUT
|
||
|
#
|
||
|
# Copyright (c) 1999 - 2023 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
|
||
|
|
||
|
import app.scodoc.sco_utils as scu
|
||
|
from app.scodoc import sco_pdf
|
||
|
from app.scodoc.sco_logos import find_logo
|
||
|
from app.scodoc.sco_pdf import SU
|
||
|
|
||
|
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.make_paras(
|
||
|
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)
|
||
|
col_widths = [None, LOGO_FOOTER_WIDTH + 2 * mm]
|
||
|
if with_page_numbers:
|
||
|
elems.append(np)
|
||
|
col_widths.append(2 * cm)
|
||
|
else:
|
||
|
elems.append("")
|
||
|
col_widths.append(8 * mm) # force marge droite
|
||
|
tab = Table([elems], style=tabstyle, colWidths=col_widths)
|
||
|
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 is not 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"],
|
||
|
)
|
||
|
super().__init__(
|
||
|
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
|
||
|
# super().beforeDrawPage(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 jury_titres(formsemestre: FormSemestre, diplome: bool) -> tuple[str, str]:
|
||
|
"""Titres du PV ou lettre de jury"""
|
||
|
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
|