forked from ScoDoc/ScoDoc
Réorganisation du code de génration de PV de jury PDF
This commit is contained in:
parent
52dfa16a69
commit
998820e671
@ -97,7 +97,7 @@ def pvjury_table_but(
|
||||
"""Table avec résultats jury BUT pour PV.
|
||||
Si etudids est None, prend tous les étudiants inscrits.
|
||||
"""
|
||||
# remplace pour le BUT la fonction sco_pvjury.pvjury_table
|
||||
# remplace pour le BUT la fonction sco_pv_forms.pvjury_table
|
||||
annee_but = (formsemestre.semestre_id + 1) // 2
|
||||
titles = {
|
||||
"nom": "Nom",
|
||||
|
@ -12,7 +12,7 @@ import numpy as np
|
||||
from app.but import jury_but
|
||||
from app.models.etudiants import Identite
|
||||
from app.models.formsemestre import FormSemestre
|
||||
from app.scodoc import sco_dict_pv_jury
|
||||
from app.scodoc import sco_pv_dict
|
||||
|
||||
|
||||
def get_jury_but_results(formsemestre: FormSemestre) -> list[dict]:
|
||||
@ -20,7 +20,7 @@ def get_jury_but_results(formsemestre: FormSemestre) -> list[dict]:
|
||||
if formsemestre.formation.referentiel_competence is None:
|
||||
# pas de ref. comp., donc pas de decisions de jury (ne lance pas d'exception)
|
||||
return []
|
||||
dpv = sco_dict_pv_jury.dict_pvjury(formsemestre.id)
|
||||
dpv = sco_pv_dict.dict_pvjury(formsemestre.id)
|
||||
rows = []
|
||||
for etudid in formsemestre.etuds_inscriptions:
|
||||
rows.append(_get_jury_but_etud_result(formsemestre, dpv, etudid))
|
||||
|
@ -43,8 +43,8 @@
|
||||
Les maquettes Apogée pour l'export des notes sont dans
|
||||
<archivedir>/apo_csv/<dept_id>/<annee_scolaire>-<sem_id>/<YYYY-MM-DD-HH-MM-SS>/<code_etape>.csv
|
||||
|
||||
Un répertoire d'archive contient des fichiers quelconques, et un fichier texte nommé _description.txt
|
||||
qui est une description (humaine, format libre) de l'archive.
|
||||
Un répertoire d'archive contient des fichiers quelconques, et un fichier texte
|
||||
nommé _description.txt qui est une description (humaine, format libre) de l'archive.
|
||||
|
||||
"""
|
||||
from typing import Union
|
||||
@ -61,7 +61,6 @@ import chardet
|
||||
|
||||
import flask
|
||||
from flask import flash, g, request, url_for
|
||||
from flask_login import current_user
|
||||
|
||||
import app.scodoc.sco_utils as scu
|
||||
from config import Config
|
||||
@ -74,12 +73,11 @@ from app.scodoc.TrivialFormulator import TrivialFormulator
|
||||
from app.scodoc.sco_exceptions import ScoPermissionDenied
|
||||
from app.scodoc import html_sco_header
|
||||
from app.scodoc import sco_bulletins_pdf
|
||||
from app.scodoc import sco_formsemestre
|
||||
from app.scodoc import sco_groups
|
||||
from app.scodoc import sco_groups_view
|
||||
from app.scodoc import sco_pvjury
|
||||
from app.scodoc import sco_dict_pv_jury
|
||||
from app.scodoc import sco_pvpdf
|
||||
from app.scodoc import sco_pv_forms
|
||||
from app.scodoc import sco_pv_lettres_inviduelles
|
||||
from app.scodoc import sco_pv_pdf
|
||||
from app.scodoc.sco_exceptions import ScoValueError
|
||||
|
||||
|
||||
@ -210,7 +208,7 @@ class BaseArchiver(object):
|
||||
self.initialize()
|
||||
filename = os.path.join(archive_id, "_description.txt")
|
||||
try:
|
||||
with open(filename) as f:
|
||||
with open(filename, encoding=scu.SCO_ENCODING) as f:
|
||||
descr = f.read()
|
||||
except UnicodeDecodeError:
|
||||
# some (old) files may have saved under exotic encodings
|
||||
@ -294,7 +292,7 @@ PVArchive = SemsArchiver()
|
||||
|
||||
def do_formsemestre_archive(
|
||||
formsemestre_id,
|
||||
group_ids=[], # si indiqué, ne prend que ces groupes
|
||||
group_ids: list[int] = None, # si indiqué, ne prend que ces groupes
|
||||
description="",
|
||||
date_jury="",
|
||||
signature=None, # pour lettres indiv
|
||||
@ -349,7 +347,8 @@ def do_formsemestre_archive(
|
||||
no_side_bar=True,
|
||||
),
|
||||
f'<h2 class="fontorange">Valeurs archivées le {date}</h2>',
|
||||
'<style type="text/css">table.notes_recapcomplet tr { color: rgb(185,70,0); }</style>',
|
||||
"""<style type="text/css">table.notes_recapcomplet tr { color: rgb(185,70,0); }
|
||||
</style>""",
|
||||
table_html,
|
||||
html_sco_header.sco_footer(),
|
||||
]
|
||||
@ -366,7 +365,7 @@ def do_formsemestre_archive(
|
||||
response = jury_but_pv.pvjury_page_but(formsemestre_id, fmt="xls")
|
||||
data = response.get_data()
|
||||
else: # formations classiques
|
||||
data = sco_pvjury.formsemestre_pvjury(
|
||||
data = sco_pv_forms.formsemestre_pvjury(
|
||||
formsemestre_id, format="xls", publish=False
|
||||
)
|
||||
if data:
|
||||
@ -382,7 +381,7 @@ def do_formsemestre_archive(
|
||||
if data:
|
||||
PVArchive.store(archive_id, "Bulletins.pdf", data)
|
||||
# Lettres individuelles (PDF):
|
||||
data = sco_pvpdf.pdf_lettres_individuelles(
|
||||
data = sco_pv_lettres_inviduelles.pdf_lettres_individuelles(
|
||||
formsemestre_id,
|
||||
etudids=etudids,
|
||||
date_jury=date_jury,
|
||||
@ -390,27 +389,23 @@ def do_formsemestre_archive(
|
||||
signature=signature,
|
||||
)
|
||||
if data:
|
||||
PVArchive.store(archive_id, "CourriersDecisions%s.pdf" % groups_filename, data)
|
||||
PVArchive.store(archive_id, f"CourriersDecisions{groups_filename}.pdf", data)
|
||||
|
||||
# PV de jury (PDF): disponible seulement en classique
|
||||
# en BUT, le PV est sous forme excel (Decisions_Jury.xlsx ci-dessus)
|
||||
if not formsemestre.formation.is_apc():
|
||||
dpv = sco_dict_pv_jury.dict_pvjury(
|
||||
formsemestre_id, etudids=etudids, with_prev=True
|
||||
)
|
||||
data = sco_pvpdf.pvjury_pdf(
|
||||
dpv,
|
||||
date_commission=date_commission,
|
||||
date_jury=date_jury,
|
||||
numero_arrete=numero_arrete,
|
||||
code_vdi=code_vdi,
|
||||
show_title=show_title,
|
||||
pv_title=pv_title,
|
||||
with_paragraph_nom=with_paragraph_nom,
|
||||
anonymous=anonymous,
|
||||
)
|
||||
if data:
|
||||
PVArchive.store(archive_id, "PV_Jury%s.pdf" % groups_filename, data)
|
||||
# PV de jury (PDF):
|
||||
data = sco_pv_pdf.pvjury_pdf(
|
||||
formsemestre,
|
||||
etudids=etudids,
|
||||
date_commission=date_commission,
|
||||
date_jury=date_jury,
|
||||
numero_arrete=numero_arrete,
|
||||
code_vdi=code_vdi,
|
||||
show_title=show_title,
|
||||
pv_title=pv_title,
|
||||
with_paragraph_nom=with_paragraph_nom,
|
||||
anonymous=anonymous,
|
||||
)
|
||||
if data:
|
||||
PVArchive.store(archive_id, f"PV_Jury{groups_filename}.pdf", data)
|
||||
|
||||
|
||||
def formsemestre_archive(formsemestre_id, group_ids: list[int] = None):
|
||||
@ -450,7 +445,11 @@ enregistrés et non modifiables, on peut les retrouver ultérieurement.
|
||||
""",
|
||||
]
|
||||
F = [
|
||||
"""<p><em>Note: les documents sont aussi affectés par les réglages sur la page "<a href="edit_preferences">Paramétrage</a>" (accessible à l'administrateur du département).</em>
|
||||
f"""<p><em>Note: les documents sont aussi affectés par les réglages sur la page
|
||||
"<a class="stdlink" href="{
|
||||
url_for("scolar.edit_preferences", scodoc_dept=g.scodoc_dept)
|
||||
}">Paramétrage</a>"
|
||||
(accessible à l'administrateur du département).</em>
|
||||
</p>""",
|
||||
html_sco_header.sco_footer(),
|
||||
]
|
||||
@ -462,7 +461,7 @@ enregistrés et non modifiables, on peut les retrouver ultérieurement.
|
||||
),
|
||||
("sep", {"input_type": "separator", "title": "Informations sur PV de jury"}),
|
||||
]
|
||||
descr += sco_pvjury.descrform_pvjury(formsemestre)
|
||||
descr += sco_pv_forms.descrform_pvjury(formsemestre)
|
||||
descr += [
|
||||
(
|
||||
"signature",
|
||||
@ -507,7 +506,7 @@ enregistrés et non modifiables, on peut les retrouver ultérieurement.
|
||||
if tf[0] == 0:
|
||||
return "\n".join(H) + "\n" + tf[1] + "\n".join(F)
|
||||
elif tf[0] == -1:
|
||||
msg = "Opération%20annulée"
|
||||
msg = "Opération annulée"
|
||||
else:
|
||||
# submit
|
||||
sf = tf[2]["signature"]
|
||||
@ -531,7 +530,7 @@ enregistrés et non modifiables, on peut les retrouver ultérieurement.
|
||||
anonymous=tf[2]["anonymous"],
|
||||
bul_version=tf[2]["bul_version"],
|
||||
)
|
||||
msg = "Nouvelle%20archive%20créée"
|
||||
msg = "Nouvelle archive créée"
|
||||
|
||||
# submitted or cancelled:
|
||||
flash(msg)
|
||||
@ -546,7 +545,6 @@ enregistrés et non modifiables, on peut les retrouver ultérieurement.
|
||||
|
||||
def formsemestre_list_archives(formsemestre_id):
|
||||
"""Page listing archives"""
|
||||
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
|
||||
sem_archive_id = formsemestre_id
|
||||
L = []
|
||||
for archive_id in PVArchive.list_obj_archives(sem_archive_id):
|
||||
|
@ -59,7 +59,7 @@ from app.scodoc import sco_evaluation_db
|
||||
from app.scodoc import sco_formsemestre
|
||||
from app.scodoc import sco_groups
|
||||
from app.scodoc import sco_preferences
|
||||
from app.scodoc import sco_dict_pv_jury
|
||||
from app.scodoc import sco_pv_dict
|
||||
from app.scodoc import sco_users
|
||||
import app.scodoc.sco_utils as scu
|
||||
from app.scodoc.sco_utils import ModuleType, fmt_note
|
||||
@ -787,7 +787,7 @@ def etud_descr_situation_semestre(
|
||||
infos["date_defaillance"] = date_def
|
||||
infos["descr_decision_jury"] = f"Défaillant{ne}"
|
||||
|
||||
dpv = sco_dict_pv_jury.dict_pvjury(formsemestre_id, etudids=[etudid])
|
||||
dpv = sco_pv_dict.dict_pvjury(formsemestre_id, etudids=[etudid])
|
||||
if dpv:
|
||||
infos["decision_sem"] = dpv["decisions"][0]["decision_sem"]
|
||||
|
||||
|
@ -40,7 +40,7 @@ from app.scodoc import html_sco_header
|
||||
from app.scodoc import sco_bac
|
||||
from app.scodoc import codes_cursus
|
||||
from app.scodoc import sco_preferences
|
||||
from app.scodoc import sco_dict_pv_jury
|
||||
from app.scodoc import sco_pv_dict
|
||||
from app.scodoc import sco_etud
|
||||
import sco_version
|
||||
from app.scodoc.gen_tables import GenTable
|
||||
@ -57,7 +57,7 @@ def _build_results_table(start_date=None, end_date=None, types_parcours=[]):
|
||||
# Décisions de jury de tous les semestres:
|
||||
dpv_by_sem = {}
|
||||
for formsemestre_id in formsemestre_ids:
|
||||
dpv_by_sem[formsemestre_id] = sco_dict_pv_jury.dict_pvjury(
|
||||
dpv_by_sem[formsemestre_id] = sco_pv_dict.dict_pvjury(
|
||||
formsemestre_id, with_parcours_decisions=True
|
||||
)
|
||||
|
||||
@ -348,7 +348,7 @@ end_date='2017-08-31'
|
||||
formsemestre_ids = get_set_formsemestre_id_dates( start_date, end_date)
|
||||
dpv_by_sem = {}
|
||||
for formsemestre_id in formsemestre_ids:
|
||||
dpv_by_sem[formsemestre_id] = sco_dict_pv_jury.dict_pvjury( formsemestre_id, with_parcours_decisions=True)
|
||||
dpv_by_sem[formsemestre_id] = sco_pv_dict.dict_pvjury( formsemestre_id, with_parcours_decisions=True)
|
||||
|
||||
semlist = [ dpv['formsemestre'] for dpv in dpv_by_sem.values() ]
|
||||
|
||||
|
@ -64,7 +64,7 @@ from app.scodoc import sco_cursus_dut
|
||||
from app.scodoc.sco_cursus_dut import etud_est_inscrit_ue
|
||||
from app.scodoc import sco_photos
|
||||
from app.scodoc import sco_preferences
|
||||
from app.scodoc import sco_dict_pv_jury
|
||||
from app.scodoc import sco_pv_dict
|
||||
|
||||
# ------------------------------------------------------------------------------------
|
||||
def formsemestre_validation_etud_form(
|
||||
@ -562,7 +562,7 @@ def formsemestre_recap_parcours_table(
|
||||
is_cur = Se.formsemestre_id == sem["formsemestre_id"]
|
||||
num_sem += 1
|
||||
|
||||
dpv = sco_dict_pv_jury.dict_pvjury(sem["formsemestre_id"], etudids=[etudid])
|
||||
dpv = sco_pv_dict.dict_pvjury(sem["formsemestre_id"], etudids=[etudid])
|
||||
pv = dpv["decisions"][0]
|
||||
decision_sem = pv["decision_sem"]
|
||||
decisions_ue = pv["decisions_ue"]
|
||||
|
@ -47,7 +47,7 @@ from app.scodoc import sco_formsemestre
|
||||
from app.scodoc import sco_formsemestre_inscriptions
|
||||
from app.scodoc import sco_groups
|
||||
from app.scodoc import sco_preferences
|
||||
from app.scodoc import sco_dict_pv_jury
|
||||
from app.scodoc import sco_pv_dict
|
||||
from app.scodoc.sco_exceptions import ScoValueError
|
||||
|
||||
|
||||
@ -137,7 +137,7 @@ def list_inscrits(formsemestre_id, with_dems=False):
|
||||
def list_etuds_from_sem(src, dst) -> list[dict]:
|
||||
"""Liste des etudiants du semestre src qui sont autorisés à passer dans le semestre dst."""
|
||||
target = dst["semestre_id"]
|
||||
dpv = sco_dict_pv_jury.dict_pvjury(src["formsemestre_id"])
|
||||
dpv = sco_pv_dict.dict_pvjury(src["formsemestre_id"])
|
||||
if not dpv:
|
||||
return []
|
||||
etuds = [
|
||||
|
@ -221,7 +221,7 @@ class ScoDocPageTemplate(PageTemplate):
|
||||
def __init__(
|
||||
self,
|
||||
document,
|
||||
pagesbookmarks={},
|
||||
pagesbookmarks: dict = None,
|
||||
author=None,
|
||||
title=None,
|
||||
subject=None,
|
||||
@ -385,6 +385,11 @@ class BulletinDocTemplate(BaseDocTemplate):
|
||||
ajoute la gestion des bookmarks
|
||||
"""
|
||||
|
||||
def __init__(self, *args, **kw):
|
||||
super().__init__(*args, **kw)
|
||||
self.current_footer = ""
|
||||
self.filigranne = None
|
||||
|
||||
# inspired by https://www.reportlab.com/snippets/13/
|
||||
def afterFlowable(self, flowable):
|
||||
"""Called by Reportlab after each flowable"""
|
||||
|
@ -27,23 +27,7 @@
|
||||
|
||||
"""Edition des PV de jury
|
||||
|
||||
PV Jury IUTV 2006: on détaillait 8 cas:
|
||||
Jury de semestre n
|
||||
On a 8 types de décisions:
|
||||
Passages:
|
||||
1. passage de ceux qui ont validés Sn-1
|
||||
2. passage avec compensation Sn-1, Sn
|
||||
3. passage sans validation de Sn avec validation d'UE
|
||||
4. passage sans validation de Sn sans validation d'UE
|
||||
|
||||
Redoublements:
|
||||
5. redoublement de Sn-1 et Sn sans validation d'UE pour Sn
|
||||
6. redoublement de Sn-1 et Sn avec validation d'UE pour Sn
|
||||
|
||||
Reports
|
||||
7. report sans validation d'UE
|
||||
|
||||
8. non validation de Sn-1 et Sn et non redoublement
|
||||
Formulaires paramétrage PV et génération des tables
|
||||
"""
|
||||
|
||||
import time
|
||||
@ -54,25 +38,20 @@ import flask
|
||||
from flask import flash, redirect, url_for
|
||||
from flask import g, request
|
||||
|
||||
from app.models import (
|
||||
Formation,
|
||||
FormSemestre,
|
||||
ScolarAutorisationInscription,
|
||||
)
|
||||
from app.models.etudiants import Identite
|
||||
from app.models import FormSemestre, Identite
|
||||
|
||||
import app.scodoc.sco_utils as scu
|
||||
import app.scodoc.notesdb as ndb
|
||||
from app.scodoc import html_sco_header
|
||||
from app.scodoc import codes_cursus
|
||||
from app.scodoc import sco_dict_pv_jury
|
||||
from app.scodoc import sco_pv_dict
|
||||
from app.scodoc import sco_etud
|
||||
from app.scodoc import sco_formsemestre
|
||||
from app.scodoc import sco_groups
|
||||
from app.scodoc import sco_groups_view
|
||||
from app.scodoc import sco_pdf
|
||||
from app.scodoc import sco_preferences
|
||||
from app.scodoc import sco_pvpdf
|
||||
from app.scodoc import sco_pv_pdf
|
||||
from app.scodoc import sco_pv_lettres_inviduelles
|
||||
from app.scodoc.gen_tables import GenTable
|
||||
from app.scodoc.codes_cursus import NO_SEMESTRE_ID
|
||||
from app.scodoc.sco_pdf import PDFLOCK
|
||||
@ -245,7 +224,7 @@ def formsemestre_pvjury(formsemestre_id, format="html", publish=True): # XXX
|
||||
|
||||
footer = html_sco_header.sco_footer()
|
||||
|
||||
dpv = sco_dict_pv_jury.dict_pvjury(formsemestre_id, with_prev=True)
|
||||
dpv = sco_pv_dict.dict_pvjury(formsemestre_id, with_prev=True)
|
||||
if not dpv:
|
||||
if format == "html":
|
||||
return (
|
||||
@ -427,7 +406,7 @@ def formsemestre_pvjury_pdf(formsemestre_id, group_ids: list[int] = None, etudid
|
||||
tf[2]["anonymous"] = bool(tf[2]["anonymous"])
|
||||
try:
|
||||
PDFLOCK.acquire()
|
||||
pdfdoc = sco_pvpdf.pvjury_pdf(
|
||||
pdfdoc = sco_pv_pdf.pvjury_pdf(
|
||||
formsemestre,
|
||||
etudids,
|
||||
numero_arrete=tf[2]["numero_arrete"],
|
||||
@ -596,7 +575,7 @@ def formsemestre_lettres_individuelles(formsemestre_id, group_ids=[]):
|
||||
signature = sf.read() # image of signature
|
||||
try:
|
||||
PDFLOCK.acquire()
|
||||
pdfdoc = sco_pvpdf.pdf_lettres_individuelles(
|
||||
pdfdoc = sco_pv_lettres_inviduelles.pdf_lettres_individuelles(
|
||||
formsemestre_id,
|
||||
etudids=etudids,
|
||||
date_jury=tf[2]["date_jury"],
|
357
app/scodoc/sco_pv_lettres_inviduelles.py
Normal file
357
app/scodoc/sco_pv_lettres_inviduelles.py
Normal file
@ -0,0 +1,357 @@
|
||||
# -*- 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 lettres individuelles de jury
|
||||
"""
|
||||
# code initialement dans sco_pvpdf.py
|
||||
|
||||
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
|
||||
from reportlab.platypus import PageBreak, Table, Image
|
||||
from reportlab.platypus.doctemplate import BaseDocTemplate
|
||||
from reportlab.lib import styles
|
||||
|
||||
from app.models import FormSemestre, Identite
|
||||
|
||||
import app.scodoc.sco_utils as scu
|
||||
from app.scodoc import sco_bulletins_pdf
|
||||
from app.scodoc import sco_pv_dict
|
||||
from app.scodoc import sco_etud
|
||||
from app.scodoc import sco_pdf
|
||||
from app.scodoc import sco_preferences
|
||||
from app.scodoc.sco_exceptions import ScoValueError
|
||||
from app.scodoc.sco_cursus_dut import SituationEtudCursus
|
||||
from app.scodoc.sco_pv_templates import CourrierIndividuelTemplate, jury_titres
|
||||
import sco_version
|
||||
|
||||
|
||||
def pdf_lettres_individuelles(
|
||||
formsemestre_id,
|
||||
etudids=None,
|
||||
date_jury="",
|
||||
date_commission="",
|
||||
signature=None,
|
||||
):
|
||||
"""Document PDF avec les lettres d'avis pour les etudiants mentionnés
|
||||
(tous ceux du semestre, ou la liste indiquée par etudids)
|
||||
Renvoie pdf data ou chaine vide si aucun etudiant avec décision de jury.
|
||||
"""
|
||||
dpv = sco_pv_dict.dict_pvjury(formsemestre_id, etudids=etudids, with_prev=True)
|
||||
if not dpv:
|
||||
return ""
|
||||
# Ajoute infos sur etudiants
|
||||
etuds = [x["identite"] for x in dpv["decisions"]]
|
||||
sco_etud.fill_etuds_info(etuds)
|
||||
#
|
||||
formsemestre: FormSemestre = FormSemestre.query.get(formsemestre_id)
|
||||
prefs = sco_preferences.SemPreferences(formsemestre_id)
|
||||
params = {
|
||||
"date_jury": date_jury,
|
||||
"date_commission": date_commission,
|
||||
"titre_formation": dpv["formation"]["titre_officiel"],
|
||||
"htab1": "8cm", # lignes à droite (entete, signature)
|
||||
"htab2": "1cm",
|
||||
}
|
||||
# copie preferences
|
||||
for name in sco_preferences.get_base_preferences().prefs_name:
|
||||
params[name] = sco_preferences.get_preference(name, formsemestre_id)
|
||||
|
||||
bookmarks = {}
|
||||
objects = [] # list of PLATYPUS objects
|
||||
npages = 0
|
||||
for decision in dpv["decisions"]:
|
||||
if (
|
||||
decision["decision_sem"]
|
||||
or decision.get("decision_annee")
|
||||
or decision.get("decision_rcue")
|
||||
): # decision prise
|
||||
etud: Identite = Identite.query.get(decision["identite"]["etudid"])
|
||||
params["nomEtud"] = etud.nomprenom
|
||||
bookmarks[npages + 1] = scu.suppress_accents(etud.nomprenom)
|
||||
try:
|
||||
objects += pdf_lettre_individuelle(
|
||||
dpv["formsemestre"], decision, etud, params, signature
|
||||
)
|
||||
except UnidentifiedImageError as exc:
|
||||
raise ScoValueError(
|
||||
"Fichier image (signature ou logo ?) invalide !"
|
||||
) from exc
|
||||
objects.append(PageBreak())
|
||||
npages += 1
|
||||
if npages == 0:
|
||||
return ""
|
||||
# Paramètres de mise en page
|
||||
margins = (
|
||||
prefs["left_margin"],
|
||||
prefs["top_margin"],
|
||||
prefs["right_margin"],
|
||||
prefs["bottom_margin"],
|
||||
)
|
||||
|
||||
# ----- Build PDF
|
||||
report = io.BytesIO() # in-memory document, no disk file
|
||||
document = BaseDocTemplate(report)
|
||||
document.addPageTemplates(
|
||||
CourrierIndividuelTemplate(
|
||||
document,
|
||||
author=f"{sco_version.SCONAME} {sco_version.SCOVERSION} (E. Viennet)",
|
||||
title=f"Lettres décision {formsemestre.titre_annee()}",
|
||||
subject="Décision jury",
|
||||
margins=margins,
|
||||
pagesbookmarks=bookmarks,
|
||||
preferences=prefs,
|
||||
)
|
||||
)
|
||||
|
||||
document.build(objects)
|
||||
data = report.getvalue()
|
||||
return data
|
||||
|
||||
|
||||
def _simulate_br(paragraph_txt: str, para="<para>") -> str:
|
||||
"""Reportlab bug turnaround (could be removed in a future version).
|
||||
p is a string with Reportlab intra-paragraph XML tags.
|
||||
Replaces <br> (currently ignored by Reportlab) by </para><para>
|
||||
Also replaces <br> by <br/>
|
||||
"""
|
||||
return ("</para>" + para).join(
|
||||
re.split(r"<.*?br.*?/>", paragraph_txt.replace("<br>", "<br/>"))
|
||||
)
|
||||
|
||||
|
||||
def _make_signature_image(signature, leftindent, formsemestre_id) -> Table:
|
||||
"crée un paragraphe avec l'image signature"
|
||||
# cree une image PIL pour avoir la taille (W,H)
|
||||
|
||||
f = io.BytesIO(signature)
|
||||
img = PILImage.open(f)
|
||||
width, height = img.size
|
||||
pdfheight = (
|
||||
1.0
|
||||
* sco_preferences.get_preference("pv_sig_image_height", formsemestre_id)
|
||||
* mm
|
||||
)
|
||||
f.seek(0, 0)
|
||||
|
||||
style = styles.ParagraphStyle({})
|
||||
style.leading = 1.0 * sco_preferences.get_preference(
|
||||
"SCOLAR_FONT_SIZE", formsemestre_id
|
||||
) # vertical space
|
||||
style.leftIndent = leftindent
|
||||
return Table(
|
||||
[("", Image(f, width=width * pdfheight / float(height), height=pdfheight))],
|
||||
colWidths=(9 * cm, 7 * cm),
|
||||
)
|
||||
|
||||
|
||||
def pdf_lettre_individuelle(sem, decision, etud: Identite, params, signature=None):
|
||||
"""
|
||||
Renvoie une liste d'objets PLATYPUS pour intégration
|
||||
dans un autre document.
|
||||
"""
|
||||
#
|
||||
formsemestre_id = sem["formsemestre_id"]
|
||||
formsemestre = FormSemestre.query.get(formsemestre_id)
|
||||
Se: SituationEtudCursus = decision["Se"]
|
||||
t, s = jury_titres(
|
||||
formsemestre, Se.parcours_validated() or not Se.semestre_non_terminal
|
||||
)
|
||||
objects = []
|
||||
style = reportlab.lib.styles.ParagraphStyle({})
|
||||
style.fontSize = 14
|
||||
style.fontName = sco_preferences.get_preference("PV_FONTNAME", formsemestre_id)
|
||||
style.leading = 18
|
||||
style.alignment = TA_LEFT
|
||||
|
||||
params["semestre_id"] = formsemestre.semestre_id
|
||||
params["decision_sem_descr"] = decision["decision_sem_descr"]
|
||||
params["type_jury"] = t # type de jury (passage ou delivrance)
|
||||
params["type_jury_abbrv"] = s # idem, abbrégé
|
||||
params["decisions_ue_descr"] = decision["decisions_ue_descr"]
|
||||
if decision["decisions_ue_nb"] > 1:
|
||||
params["decisions_ue_descr_plural"] = "s"
|
||||
else:
|
||||
params["decisions_ue_descr_plural"] = ""
|
||||
|
||||
params["INSTITUTION_CITY"] = (
|
||||
sco_preferences.get_preference("INSTITUTION_CITY", formsemestre_id) or ""
|
||||
)
|
||||
|
||||
if decision["prev_decision_sem"]:
|
||||
params["prev_semestre_id"] = decision["prev"]["semestre_id"]
|
||||
|
||||
params["prev_decision_sem_txt"] = ""
|
||||
params["decision_orig"] = ""
|
||||
|
||||
params.update(decision["identite"])
|
||||
# fix domicile
|
||||
if params["domicile"]:
|
||||
params["domicile"] = params["domicile"].replace("\\n", "<br/>")
|
||||
|
||||
# UE capitalisées:
|
||||
if decision["decisions_ue"] and decision["decisions_ue_descr"]:
|
||||
params["decision_ue_txt"] = (
|
||||
"""<b>Unité%(decisions_ue_descr_plural)s d'Enseignement %(decision_orig)s capitalisée%(decisions_ue_descr_plural)s : %(decisions_ue_descr)s</b>"""
|
||||
% params
|
||||
)
|
||||
else:
|
||||
params["decision_ue_txt"] = ""
|
||||
# Mention
|
||||
params["mention"] = decision["mention"]
|
||||
# Informations sur compensations
|
||||
if decision["observation"]:
|
||||
params["observation_txt"] = (
|
||||
"""<b>Observation :</b> %(observation)s.""" % decision
|
||||
)
|
||||
else:
|
||||
params["observation_txt"] = ""
|
||||
# Autorisations de passage
|
||||
if decision["autorisations"] and not Se.parcours_validated():
|
||||
if len(decision["autorisations"]) > 1:
|
||||
s = "s"
|
||||
else:
|
||||
s = ""
|
||||
params[
|
||||
"autorisations_txt"
|
||||
] = """Vous êtes autorisé%s à continuer dans le%s semestre%s : <b>%s</b>""" % (
|
||||
etud.e,
|
||||
s,
|
||||
s,
|
||||
decision["autorisations_descr"],
|
||||
)
|
||||
else:
|
||||
params["autorisations_txt"] = ""
|
||||
|
||||
if decision["decision_sem"] and Se.parcours_validated():
|
||||
params["diplome_txt"] = (
|
||||
"""Vous avez donc obtenu le diplôme : <b>%(titre_formation)s</b>""" % params
|
||||
)
|
||||
else:
|
||||
params["diplome_txt"] = ""
|
||||
|
||||
# Les fonctions ci-dessous ajoutent ou modifient des champs:
|
||||
if formsemestre.formation.is_apc():
|
||||
# ajout champs spécifiques PV BUT
|
||||
add_apc_infos(formsemestre, params, decision)
|
||||
else:
|
||||
# ajout champs spécifiques PV DUT
|
||||
add_classic_infos(formsemestre, params, decision)
|
||||
|
||||
# Corps de la lettre:
|
||||
objects += sco_bulletins_pdf.process_field(
|
||||
sco_preferences.get_preference("PV_LETTER_TEMPLATE", sem["formsemestre_id"]),
|
||||
params,
|
||||
style,
|
||||
suppress_empty_pars=True,
|
||||
)
|
||||
|
||||
# Signature:
|
||||
# nota: si semestre terminal, signature par directeur IUT, sinon, signature par
|
||||
# chef de département.
|
||||
if Se.semestre_non_terminal:
|
||||
sig = (
|
||||
sco_preferences.get_preference(
|
||||
"PV_LETTER_PASSAGE_SIGNATURE", formsemestre_id
|
||||
)
|
||||
or ""
|
||||
) % params
|
||||
sig = _simulate_br(sig, '<para leftindent="%(htab1)s">')
|
||||
objects += sco_pdf.make_paras(
|
||||
(
|
||||
"""<para leftindent="%(htab1)s" spaceBefore="25mm">"""
|
||||
+ sig
|
||||
+ """</para>"""
|
||||
)
|
||||
% params,
|
||||
style,
|
||||
)
|
||||
else:
|
||||
sig = (
|
||||
sco_preferences.get_preference(
|
||||
"PV_LETTER_DIPLOMA_SIGNATURE", formsemestre_id
|
||||
)
|
||||
or ""
|
||||
) % params
|
||||
sig = _simulate_br(sig, '<para leftindent="%(htab1)s">')
|
||||
objects += sco_pdf.make_paras(
|
||||
(
|
||||
"""<para leftindent="%(htab1)s" spaceBefore="25mm">"""
|
||||
+ sig
|
||||
+ """</para>"""
|
||||
)
|
||||
% params,
|
||||
style,
|
||||
)
|
||||
|
||||
if signature:
|
||||
try:
|
||||
objects.append(
|
||||
_make_signature_image(signature, params["htab1"], formsemestre_id)
|
||||
)
|
||||
except UnidentifiedImageError as exc:
|
||||
raise ScoValueError("Image signature invalide !") from exc
|
||||
|
||||
return objects
|
||||
|
||||
|
||||
def add_classic_infos(formsemestre: FormSemestre, params: dict, decision: dict):
|
||||
"""Ajoute les champs pour les formations classiques, donc avec codes semestres"""
|
||||
if decision["prev_decision_sem"]:
|
||||
params["prev_code_descr"] = decision["prev_code_descr"]
|
||||
params[
|
||||
"prev_decision_sem_txt"
|
||||
] = f"""<b>Décision du semestre antérieur S{params['prev_semestre_id']} :</b> {
|
||||
params['prev_code_descr']}"""
|
||||
# Décision semestre courant:
|
||||
if formsemestre.semestre_id >= 0:
|
||||
params["decision_orig"] = f"du semestre S{formsemestre.semestre_id}"
|
||||
else:
|
||||
params["decision_orig"] = ""
|
||||
|
||||
|
||||
def add_apc_infos(formsemestre: FormSemestre, params: dict, decision: dict):
|
||||
"""Ajoute les champs pour les formations APC (BUT), donc avec codes RCUE et année"""
|
||||
annee_but = (formsemestre.semestre_id + 1) // 2
|
||||
params["decision_orig"] = f"année BUT{annee_but}"
|
||||
if decision is None:
|
||||
params["decision_sem_descr"] = ""
|
||||
params["decision_ue_txt"] = ""
|
||||
else:
|
||||
decision_annee = decision.get("decision_annee") or {}
|
||||
params["decision_sem_descr"] = decision_annee.get("code") or ""
|
||||
params[
|
||||
"decision_ue_txt"
|
||||
] = f"""{params["decision_ue_txt"]}<br/>
|
||||
<b>Niveaux de compétences:</b><br/> {decision.get("descr_decisions_rcue") or ""}
|
||||
"""
|
340
app/scodoc/sco_pv_pdf.py
Normal file
340
app/scodoc/sco_pv_pdf.py
Normal file
@ -0,0 +1,340 @@
|
||||
# -*- 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
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
"""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 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,
|
||||
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=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=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),
|
||||
)
|
||||
)
|
||||
|
||||
document.build(objects)
|
||||
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,
|
||||
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)"
|
||||
} - Session unique {formsemestre.annee_scolaire()}</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
|
||||
)
|
||||
|
||||
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,
|
||||
)
|
||||
|
||||
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/>"
|
||||
)
|
||||
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
|
344
app/scodoc/sco_pv_templates.py
Normal file
344
app/scodoc/sco_pv_templates.py
Normal file
@ -0,0 +1,344 @@
|
||||
# -*- 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
|
@ -1,942 +0,0 @@
|
||||
# -*- 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, Identite
|
||||
|
||||
import app.scodoc.sco_utils as scu
|
||||
from app.scodoc import sco_bulletins_pdf
|
||||
from app.scodoc import codes_cursus
|
||||
from app.scodoc import sco_dict_pv_jury
|
||||
from app.scodoc import sco_etud
|
||||
from app.scodoc import sco_pdf
|
||||
from app.scodoc import sco_preferences
|
||||
from app.scodoc.sco_exceptions import ScoValueError
|
||||
from app.scodoc.sco_logos import find_logo
|
||||
from app.scodoc.sco_cursus_dut import SituationEtudCursus
|
||||
from app.scodoc.sco_pdf import SU
|
||||
import sco_version
|
||||
|
||||
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)
|
||||
colWidths = [None, LOGO_FOOTER_WIDTH + 2 * mm]
|
||||
if with_page_numbers:
|
||||
elems.append(np)
|
||||
colWidths.append(2 * cm)
|
||||
else:
|
||||
elems.append("")
|
||||
colWidths.append(8 * mm) # force marge droite
|
||||
tab = Table([elems], style=tabstyle, colWidths=colWidths)
|
||||
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 != 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"],
|
||||
)
|
||||
CourrierIndividuelTemplate.__init__(
|
||||
self,
|
||||
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
|
||||
CourrierIndividuelTemplate.beforeDrawPage(self, 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 _simulate_br(paragraph_txt: str, para="<para>") -> str:
|
||||
"""Reportlab bug turnaround (could be removed in a future version).
|
||||
p is a string with Reportlab intra-paragraph XML tags.
|
||||
Replaces <br> (currently ignored by Reportlab) by </para><para>
|
||||
Also replaces <br> by <br/>
|
||||
"""
|
||||
return ("</para>" + para).join(
|
||||
re.split(r"<.*?br.*?/>", paragraph_txt.replace("<br>", "<br/>"))
|
||||
)
|
||||
|
||||
|
||||
def _make_signature_image(signature, leftindent, formsemestre_id) -> Table:
|
||||
"crée un paragraphe avec l'image signature"
|
||||
# cree une image PIL pour avoir la taille (W,H)
|
||||
|
||||
f = io.BytesIO(signature)
|
||||
img = PILImage.open(f)
|
||||
width, height = img.size
|
||||
pdfheight = (
|
||||
1.0
|
||||
* sco_preferences.get_preference("pv_sig_image_height", formsemestre_id)
|
||||
* mm
|
||||
)
|
||||
f.seek(0, 0)
|
||||
|
||||
style = styles.ParagraphStyle({})
|
||||
style.leading = 1.0 * sco_preferences.get_preference(
|
||||
"SCOLAR_FONT_SIZE", formsemestre_id
|
||||
) # vertical space
|
||||
style.leftIndent = leftindent
|
||||
return Table(
|
||||
[("", Image(f, width=width * pdfheight / float(height), height=pdfheight))],
|
||||
colWidths=(9 * cm, 7 * cm),
|
||||
)
|
||||
|
||||
|
||||
def pdf_lettres_individuelles(
|
||||
formsemestre_id,
|
||||
etudids=None,
|
||||
date_jury="",
|
||||
date_commission="",
|
||||
signature=None,
|
||||
):
|
||||
"""Document PDF avec les lettres d'avis pour les etudiants mentionnés
|
||||
(tous ceux du semestre, ou la liste indiquée par etudids)
|
||||
Renvoie pdf data ou chaine vide si aucun etudiant avec décision de jury.
|
||||
"""
|
||||
dpv = sco_dict_pv_jury.dict_pvjury(formsemestre_id, etudids=etudids, with_prev=True)
|
||||
if not dpv:
|
||||
return ""
|
||||
# Ajoute infos sur etudiants
|
||||
etuds = [x["identite"] for x in dpv["decisions"]]
|
||||
sco_etud.fill_etuds_info(etuds)
|
||||
#
|
||||
formsemestre: FormSemestre = FormSemestre.query.get(formsemestre_id)
|
||||
prefs = sco_preferences.SemPreferences(formsemestre_id)
|
||||
params = {
|
||||
"date_jury": date_jury,
|
||||
"date_commission": date_commission,
|
||||
"titre_formation": dpv["formation"]["titre_officiel"],
|
||||
"htab1": "8cm", # lignes à droite (entete, signature)
|
||||
"htab2": "1cm",
|
||||
}
|
||||
# copie preferences
|
||||
for name in sco_preferences.get_base_preferences().prefs_name:
|
||||
params[name] = sco_preferences.get_preference(name, formsemestre_id)
|
||||
|
||||
bookmarks = {}
|
||||
objects = [] # list of PLATYPUS objects
|
||||
npages = 0
|
||||
for decision in dpv["decisions"]:
|
||||
if (
|
||||
decision["decision_sem"]
|
||||
or decision.get("decision_annee")
|
||||
or decision.get("decision_rcue")
|
||||
): # decision prise
|
||||
etud: Identite = Identite.query.get(decision["identite"]["etudid"])
|
||||
params["nomEtud"] = etud.nomprenom
|
||||
bookmarks[npages + 1] = scu.suppress_accents(etud.nomprenom)
|
||||
try:
|
||||
objects += pdf_lettre_individuelle(
|
||||
dpv["formsemestre"], decision, etud, params, signature
|
||||
)
|
||||
except UnidentifiedImageError as exc:
|
||||
raise ScoValueError(
|
||||
"Fichier image (signature ou logo ?) invalide !"
|
||||
) from exc
|
||||
objects.append(PageBreak())
|
||||
npages += 1
|
||||
if npages == 0:
|
||||
return ""
|
||||
# Paramètres de mise en page
|
||||
margins = (
|
||||
prefs["left_margin"],
|
||||
prefs["top_margin"],
|
||||
prefs["right_margin"],
|
||||
prefs["bottom_margin"],
|
||||
)
|
||||
|
||||
# ----- Build PDF
|
||||
report = io.BytesIO() # in-memory document, no disk file
|
||||
document = BaseDocTemplate(report)
|
||||
document.addPageTemplates(
|
||||
CourrierIndividuelTemplate(
|
||||
document,
|
||||
author=f"{sco_version.SCONAME} {sco_version.SCOVERSION} (E. Viennet)",
|
||||
title=f"Lettres décision {formsemestre.titre_annee()}",
|
||||
subject="Décision jury",
|
||||
margins=margins,
|
||||
pagesbookmarks=bookmarks,
|
||||
preferences=prefs,
|
||||
)
|
||||
)
|
||||
|
||||
document.build(objects)
|
||||
data = report.getvalue()
|
||||
return data
|
||||
|
||||
|
||||
def _descr_jury(formsemestre: FormSemestre, diplome):
|
||||
|
||||
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
|
||||
|
||||
|
||||
def pdf_lettre_individuelle(sem, decision, etud: Identite, params, signature=None):
|
||||
"""
|
||||
Renvoie une liste d'objets PLATYPUS pour intégration
|
||||
dans un autre document.
|
||||
"""
|
||||
#
|
||||
formsemestre_id = sem["formsemestre_id"]
|
||||
formsemestre = FormSemestre.query.get(formsemestre_id)
|
||||
Se: SituationEtudCursus = decision["Se"]
|
||||
t, s = _descr_jury(
|
||||
formsemestre, Se.parcours_validated() or not Se.semestre_non_terminal
|
||||
)
|
||||
objects = []
|
||||
style = reportlab.lib.styles.ParagraphStyle({})
|
||||
style.fontSize = 14
|
||||
style.fontName = sco_preferences.get_preference("PV_FONTNAME", formsemestre_id)
|
||||
style.leading = 18
|
||||
style.alignment = TA_LEFT
|
||||
|
||||
params["semestre_id"] = formsemestre.semestre_id
|
||||
params["decision_sem_descr"] = decision["decision_sem_descr"]
|
||||
params["type_jury"] = t # type de jury (passage ou delivrance)
|
||||
params["type_jury_abbrv"] = s # idem, abbrégé
|
||||
params["decisions_ue_descr"] = decision["decisions_ue_descr"]
|
||||
if decision["decisions_ue_nb"] > 1:
|
||||
params["decisions_ue_descr_plural"] = "s"
|
||||
else:
|
||||
params["decisions_ue_descr_plural"] = ""
|
||||
|
||||
params["INSTITUTION_CITY"] = (
|
||||
sco_preferences.get_preference("INSTITUTION_CITY", formsemestre_id) or ""
|
||||
)
|
||||
|
||||
if decision["prev_decision_sem"]:
|
||||
params["prev_semestre_id"] = decision["prev"]["semestre_id"]
|
||||
|
||||
params["prev_decision_sem_txt"] = ""
|
||||
params["decision_orig"] = ""
|
||||
|
||||
params.update(decision["identite"])
|
||||
# fix domicile
|
||||
if params["domicile"]:
|
||||
params["domicile"] = params["domicile"].replace("\\n", "<br/>")
|
||||
|
||||
# UE capitalisées:
|
||||
if decision["decisions_ue"] and decision["decisions_ue_descr"]:
|
||||
params["decision_ue_txt"] = (
|
||||
"""<b>Unité%(decisions_ue_descr_plural)s d'Enseignement %(decision_orig)s capitalisée%(decisions_ue_descr_plural)s : %(decisions_ue_descr)s</b>"""
|
||||
% params
|
||||
)
|
||||
else:
|
||||
params["decision_ue_txt"] = ""
|
||||
# Mention
|
||||
params["mention"] = decision["mention"]
|
||||
# Informations sur compensations
|
||||
if decision["observation"]:
|
||||
params["observation_txt"] = (
|
||||
"""<b>Observation :</b> %(observation)s.""" % decision
|
||||
)
|
||||
else:
|
||||
params["observation_txt"] = ""
|
||||
# Autorisations de passage
|
||||
if decision["autorisations"] and not Se.parcours_validated():
|
||||
if len(decision["autorisations"]) > 1:
|
||||
s = "s"
|
||||
else:
|
||||
s = ""
|
||||
params[
|
||||
"autorisations_txt"
|
||||
] = """Vous êtes autorisé%s à continuer dans le%s semestre%s : <b>%s</b>""" % (
|
||||
etud.e,
|
||||
s,
|
||||
s,
|
||||
decision["autorisations_descr"],
|
||||
)
|
||||
else:
|
||||
params["autorisations_txt"] = ""
|
||||
|
||||
if decision["decision_sem"] and Se.parcours_validated():
|
||||
params["diplome_txt"] = (
|
||||
"""Vous avez donc obtenu le diplôme : <b>%(titre_formation)s</b>""" % params
|
||||
)
|
||||
else:
|
||||
params["diplome_txt"] = ""
|
||||
|
||||
# Les fonctions ci-dessous ajoutent ou modifient des champs:
|
||||
if formsemestre.formation.is_apc():
|
||||
# ajout champs spécifiques PV BUT
|
||||
add_apc_infos(formsemestre, params, decision)
|
||||
else:
|
||||
# ajout champs spécifiques PV DUT
|
||||
add_classic_infos(formsemestre, params, decision)
|
||||
|
||||
# Corps de la lettre:
|
||||
objects += sco_bulletins_pdf.process_field(
|
||||
sco_preferences.get_preference("PV_LETTER_TEMPLATE", sem["formsemestre_id"]),
|
||||
params,
|
||||
style,
|
||||
suppress_empty_pars=True,
|
||||
)
|
||||
|
||||
# Signature:
|
||||
# nota: si semestre terminal, signature par directeur IUT, sinon, signature par
|
||||
# chef de département.
|
||||
if Se.semestre_non_terminal:
|
||||
sig = (
|
||||
sco_preferences.get_preference(
|
||||
"PV_LETTER_PASSAGE_SIGNATURE", formsemestre_id
|
||||
)
|
||||
or ""
|
||||
) % params
|
||||
sig = _simulate_br(sig, '<para leftindent="%(htab1)s">')
|
||||
objects += sco_pdf.make_paras(
|
||||
(
|
||||
"""<para leftindent="%(htab1)s" spaceBefore="25mm">"""
|
||||
+ sig
|
||||
+ """</para>"""
|
||||
)
|
||||
% params,
|
||||
style,
|
||||
)
|
||||
else:
|
||||
sig = (
|
||||
sco_preferences.get_preference(
|
||||
"PV_LETTER_DIPLOMA_SIGNATURE", formsemestre_id
|
||||
)
|
||||
or ""
|
||||
) % params
|
||||
sig = _simulate_br(sig, '<para leftindent="%(htab1)s">')
|
||||
objects += sco_pdf.make_paras(
|
||||
(
|
||||
"""<para leftindent="%(htab1)s" spaceBefore="25mm">"""
|
||||
+ sig
|
||||
+ """</para>"""
|
||||
)
|
||||
% params,
|
||||
style,
|
||||
)
|
||||
|
||||
if signature:
|
||||
try:
|
||||
objects.append(
|
||||
_make_signature_image(signature, params["htab1"], formsemestre_id)
|
||||
)
|
||||
except UnidentifiedImageError as exc:
|
||||
raise ScoValueError("Image signature invalide !") from exc
|
||||
|
||||
return objects
|
||||
|
||||
|
||||
def add_classic_infos(formsemestre: FormSemestre, params: dict, decision: dict):
|
||||
"""Ajoute les champs pour les formations classiques, donc avec codes semestres"""
|
||||
if decision["prev_decision_sem"]:
|
||||
params["prev_code_descr"] = decision["prev_code_descr"]
|
||||
params[
|
||||
"prev_decision_sem_txt"
|
||||
] = f"""<b>Décision du semestre antérieur S{params['prev_semestre_id']} :</b> {params['prev_code_descr']}"""
|
||||
# Décision semestre courant:
|
||||
if formsemestre.semestre_id >= 0:
|
||||
params["decision_orig"] = f"du semestre S{formsemestre.semestre_id}"
|
||||
else:
|
||||
params["decision_orig"] = ""
|
||||
|
||||
|
||||
def add_apc_infos(formsemestre: FormSemestre, params: dict, decision: dict):
|
||||
"""Ajoute les champs pour les formations APC (BUT), donc avec codes RCUE et année"""
|
||||
annee_but = (formsemestre.semestre_id + 1) // 2
|
||||
params["decision_orig"] = f"année BUT{annee_but}"
|
||||
if decision is None:
|
||||
params["decision_sem_descr"] = ""
|
||||
params["decision_ue_txt"] = ""
|
||||
else:
|
||||
decision_annee = decision.get("decision_annee") or {}
|
||||
params["decision_sem_descr"] = decision_annee.get("code") or ""
|
||||
params[
|
||||
"decision_ue_txt"
|
||||
] = f"""{params["decision_ue_txt"]}<br/>
|
||||
<b>Niveaux de compétences:</b><br/> {decision.get("descr_decisions_rcue") or ""}
|
||||
"""
|
||||
|
||||
|
||||
# ----------------------------------------------
|
||||
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,
|
||||
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=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=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),
|
||||
)
|
||||
)
|
||||
|
||||
document.build(objects)
|
||||
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,
|
||||
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_pvjury
|
||||
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, _ = _descr_jury(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)"
|
||||
} - Session unique {formsemestre.annee_scolaire()}</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
|
||||
)
|
||||
|
||||
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,
|
||||
)
|
||||
|
||||
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/>"
|
||||
)
|
||||
columns_ids = list(titles.keys())
|
||||
a_diplome = codes_cursus.ADM in [row.get("diplome") for row in rows]
|
||||
else:
|
||||
dpv = sco_dict_pv_jury.dict_pvjury(
|
||||
formsemestre.id, etudids=etudids, with_prev=True
|
||||
)
|
||||
if not dpv:
|
||||
return [], False
|
||||
rows, titles, columns_ids = sco_pvjury.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
|
@ -54,7 +54,6 @@ from app.scodoc import sco_etud
|
||||
from app.scodoc import sco_formsemestre
|
||||
from app.scodoc import sco_formsemestre_inscriptions
|
||||
from app.scodoc import sco_preferences
|
||||
from app.scodoc import sco_pvjury
|
||||
import sco_version
|
||||
from app.scodoc.gen_tables import GenTable
|
||||
from app import log
|
||||
|
@ -15,6 +15,19 @@
|
||||
Information et documentation sur <a href="https://scodoc.org" target="_blank">scodoc.org</a>.
|
||||
</p>
|
||||
|
||||
<p>Le logiciel est distribué sous
|
||||
<a href="https://www.gnu.org/licenses/old-licenses/gpl-2.0.html">licence GNU
|
||||
GPL v2</a>. <em>ScoDoc est un logiciel réalisé dans l'espoir d'être utile
|
||||
mais distribué "en l'état" sans aucune garantie de quelque nature que ce
|
||||
soit, expresse ou ou implicite, y compris, mais sans y être limité, les
|
||||
garanties implicites de commerciabilité et de la conformité a une
|
||||
utilisation particulière. Vous assumez la totalité des risques liés à la
|
||||
qualité et aux performances du programme. Si le programme se révélait
|
||||
défectueux, le coût de l'entretien, des réparations ou des corrections
|
||||
nécessaires vous incombent intégralement.
|
||||
</em>
|
||||
</p>
|
||||
|
||||
<h2>Dernières évolutions</h2>
|
||||
|
||||
{{ news|safe }}
|
||||
|
@ -122,12 +122,11 @@ from app.scodoc import sco_lycee
|
||||
from app.scodoc import sco_moduleimpl
|
||||
from app.scodoc import sco_moduleimpl_inscriptions
|
||||
from app.scodoc import sco_moduleimpl_status
|
||||
from app.scodoc import sco_permissions_check
|
||||
from app.scodoc import sco_placement
|
||||
from app.scodoc import sco_poursuite_dut
|
||||
from app.scodoc import sco_preferences
|
||||
from app.scodoc import sco_prepajury
|
||||
from app.scodoc import sco_pvjury
|
||||
from app.scodoc import sco_pv_forms
|
||||
from app.scodoc import sco_recapcomplet
|
||||
from app.scodoc import sco_report
|
||||
from app.scodoc import sco_report_but
|
||||
@ -2803,7 +2802,9 @@ def formsemestre_validation_suppress_etud(
|
||||
|
||||
|
||||
# ------------- PV de JURY et archives
|
||||
sco_publish("/formsemestre_pvjury", sco_pvjury.formsemestre_pvjury, Permission.ScoView)
|
||||
sco_publish(
|
||||
"/formsemestre_pvjury", sco_pv_forms.formsemestre_pvjury, Permission.ScoView
|
||||
)
|
||||
|
||||
sco_publish("/pvjury_page_but", jury_but_pv.pvjury_page_but, Permission.ScoView)
|
||||
|
||||
@ -2913,12 +2914,12 @@ def formsemestre_jury_but_erase(
|
||||
|
||||
sco_publish(
|
||||
"/formsemestre_lettres_individuelles",
|
||||
sco_pvjury.formsemestre_lettres_individuelles,
|
||||
sco_pv_forms.formsemestre_lettres_individuelles,
|
||||
Permission.ScoView,
|
||||
methods=["GET", "POST"],
|
||||
)
|
||||
sco_publish(
|
||||
"/formsemestre_pvjury_pdf", sco_pvjury.formsemestre_pvjury_pdf, Permission.ScoView
|
||||
"/formsemestre_pvjury_pdf", sco_pv_forms.formsemestre_pvjury_pdf, Permission.ScoView
|
||||
)
|
||||
sco_publish(
|
||||
"/feuille_preparation_jury",
|
||||
|
@ -6,6 +6,15 @@ SCOVERSION = "9.4.45"
|
||||
SCONAME = "ScoDoc"
|
||||
|
||||
SCONEWS = """
|
||||
<h4>Année 2023</h4>
|
||||
<ul>
|
||||
<li>ScoDoc 9.4</li>
|
||||
<ul>
|
||||
<li>Améliorations des tableaux récapitulatifs</li>
|
||||
<li>Nouvelle interface de gestions des groupes (S. Lehmann)</li>
|
||||
<li>Enrichissement des jurys BUT et des procès-verbaux associés.</li>
|
||||
</ul>
|
||||
</ul>
|
||||
<h4>Année 2022</h4>
|
||||
<ul>
|
||||
<li>ScoDoc 9.4</li>
|
||||
@ -14,7 +23,7 @@ SCONEWS = """
|
||||
</ul>
|
||||
<li>ScoDoc 9.3</li>
|
||||
<ul>
|
||||
<li>Nouvelle API REST pour connecter ScoDoc à d'autres applications<li>
|
||||
<li>Nouvelle API REST pour connecter ScoDoc à d'autres applications</li>
|
||||
<li>Module de gestion des relations avec les entreprises</li>
|
||||
<li>Prise en charge des parcours BUT</li>
|
||||
<li>Association des UEs aux compétences du référentiel</li>
|
||||
|
@ -29,7 +29,7 @@ from app.models import (
|
||||
UniteEns,
|
||||
)
|
||||
from app.scodoc import sco_utils as scu
|
||||
from app.scodoc import sco_dict_pv_jury
|
||||
from app.scodoc import sco_pv_dict
|
||||
|
||||
|
||||
def setup_formation_referentiel(formation: Formation, refcomp_infos: dict):
|
||||
@ -308,7 +308,7 @@ def but_test_jury(formsemestre: FormSemestre, doc: dict):
|
||||
but_compare_decisions_annee(deca, deca_att)
|
||||
if "autorisations_inscription" in doc_formsemestre["attendu"]:
|
||||
if dpv is None: # lazy load
|
||||
dpv = sco_dict_pv_jury.dict_pvjury(formsemestre.id)
|
||||
dpv = sco_pv_dict.dict_pvjury(formsemestre.id)
|
||||
check_autorisations_inscription(
|
||||
etud, dpv, doc_formsemestre["attendu"]["autorisations_inscription"]
|
||||
)
|
||||
|
Loading…
Reference in New Issue
Block a user