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.
|
"""Table avec résultats jury BUT pour PV.
|
||||||
Si etudids est None, prend tous les étudiants inscrits.
|
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
|
annee_but = (formsemestre.semestre_id + 1) // 2
|
||||||
titles = {
|
titles = {
|
||||||
"nom": "Nom",
|
"nom": "Nom",
|
||||||
|
@ -12,7 +12,7 @@ import numpy as np
|
|||||||
from app.but import jury_but
|
from app.but import jury_but
|
||||||
from app.models.etudiants import Identite
|
from app.models.etudiants import Identite
|
||||||
from app.models.formsemestre import FormSemestre
|
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]:
|
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:
|
if formsemestre.formation.referentiel_competence is None:
|
||||||
# pas de ref. comp., donc pas de decisions de jury (ne lance pas d'exception)
|
# pas de ref. comp., donc pas de decisions de jury (ne lance pas d'exception)
|
||||||
return []
|
return []
|
||||||
dpv = sco_dict_pv_jury.dict_pvjury(formsemestre.id)
|
dpv = sco_pv_dict.dict_pvjury(formsemestre.id)
|
||||||
rows = []
|
rows = []
|
||||||
for etudid in formsemestre.etuds_inscriptions:
|
for etudid in formsemestre.etuds_inscriptions:
|
||||||
rows.append(_get_jury_but_etud_result(formsemestre, dpv, etudid))
|
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
|
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
|
<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
|
Un répertoire d'archive contient des fichiers quelconques, et un fichier texte
|
||||||
qui est une description (humaine, format libre) de l'archive.
|
nommé _description.txt qui est une description (humaine, format libre) de l'archive.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
from typing import Union
|
from typing import Union
|
||||||
@ -61,7 +61,6 @@ import chardet
|
|||||||
|
|
||||||
import flask
|
import flask
|
||||||
from flask import flash, g, request, url_for
|
from flask import flash, g, request, url_for
|
||||||
from flask_login import current_user
|
|
||||||
|
|
||||||
import app.scodoc.sco_utils as scu
|
import app.scodoc.sco_utils as scu
|
||||||
from config import Config
|
from config import Config
|
||||||
@ -74,12 +73,11 @@ from app.scodoc.TrivialFormulator import TrivialFormulator
|
|||||||
from app.scodoc.sco_exceptions import ScoPermissionDenied
|
from app.scodoc.sco_exceptions import ScoPermissionDenied
|
||||||
from app.scodoc import html_sco_header
|
from app.scodoc import html_sco_header
|
||||||
from app.scodoc import sco_bulletins_pdf
|
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
|
||||||
from app.scodoc import sco_groups_view
|
from app.scodoc import sco_groups_view
|
||||||
from app.scodoc import sco_pvjury
|
from app.scodoc import sco_pv_forms
|
||||||
from app.scodoc import sco_dict_pv_jury
|
from app.scodoc import sco_pv_lettres_inviduelles
|
||||||
from app.scodoc import sco_pvpdf
|
from app.scodoc import sco_pv_pdf
|
||||||
from app.scodoc.sco_exceptions import ScoValueError
|
from app.scodoc.sco_exceptions import ScoValueError
|
||||||
|
|
||||||
|
|
||||||
@ -210,7 +208,7 @@ class BaseArchiver(object):
|
|||||||
self.initialize()
|
self.initialize()
|
||||||
filename = os.path.join(archive_id, "_description.txt")
|
filename = os.path.join(archive_id, "_description.txt")
|
||||||
try:
|
try:
|
||||||
with open(filename) as f:
|
with open(filename, encoding=scu.SCO_ENCODING) as f:
|
||||||
descr = f.read()
|
descr = f.read()
|
||||||
except UnicodeDecodeError:
|
except UnicodeDecodeError:
|
||||||
# some (old) files may have saved under exotic encodings
|
# some (old) files may have saved under exotic encodings
|
||||||
@ -294,7 +292,7 @@ PVArchive = SemsArchiver()
|
|||||||
|
|
||||||
def do_formsemestre_archive(
|
def do_formsemestre_archive(
|
||||||
formsemestre_id,
|
formsemestre_id,
|
||||||
group_ids=[], # si indiqué, ne prend que ces groupes
|
group_ids: list[int] = None, # si indiqué, ne prend que ces groupes
|
||||||
description="",
|
description="",
|
||||||
date_jury="",
|
date_jury="",
|
||||||
signature=None, # pour lettres indiv
|
signature=None, # pour lettres indiv
|
||||||
@ -349,7 +347,8 @@ def do_formsemestre_archive(
|
|||||||
no_side_bar=True,
|
no_side_bar=True,
|
||||||
),
|
),
|
||||||
f'<h2 class="fontorange">Valeurs archivées le {date}</h2>',
|
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,
|
table_html,
|
||||||
html_sco_header.sco_footer(),
|
html_sco_header.sco_footer(),
|
||||||
]
|
]
|
||||||
@ -366,7 +365,7 @@ def do_formsemestre_archive(
|
|||||||
response = jury_but_pv.pvjury_page_but(formsemestre_id, fmt="xls")
|
response = jury_but_pv.pvjury_page_but(formsemestre_id, fmt="xls")
|
||||||
data = response.get_data()
|
data = response.get_data()
|
||||||
else: # formations classiques
|
else: # formations classiques
|
||||||
data = sco_pvjury.formsemestre_pvjury(
|
data = sco_pv_forms.formsemestre_pvjury(
|
||||||
formsemestre_id, format="xls", publish=False
|
formsemestre_id, format="xls", publish=False
|
||||||
)
|
)
|
||||||
if data:
|
if data:
|
||||||
@ -382,7 +381,7 @@ def do_formsemestre_archive(
|
|||||||
if data:
|
if data:
|
||||||
PVArchive.store(archive_id, "Bulletins.pdf", data)
|
PVArchive.store(archive_id, "Bulletins.pdf", data)
|
||||||
# Lettres individuelles (PDF):
|
# Lettres individuelles (PDF):
|
||||||
data = sco_pvpdf.pdf_lettres_individuelles(
|
data = sco_pv_lettres_inviduelles.pdf_lettres_individuelles(
|
||||||
formsemestre_id,
|
formsemestre_id,
|
||||||
etudids=etudids,
|
etudids=etudids,
|
||||||
date_jury=date_jury,
|
date_jury=date_jury,
|
||||||
@ -390,16 +389,12 @@ def do_formsemestre_archive(
|
|||||||
signature=signature,
|
signature=signature,
|
||||||
)
|
)
|
||||||
if data:
|
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
|
# PV de jury (PDF):
|
||||||
# en BUT, le PV est sous forme excel (Decisions_Jury.xlsx ci-dessus)
|
data = sco_pv_pdf.pvjury_pdf(
|
||||||
if not formsemestre.formation.is_apc():
|
formsemestre,
|
||||||
dpv = sco_dict_pv_jury.dict_pvjury(
|
etudids=etudids,
|
||||||
formsemestre_id, etudids=etudids, with_prev=True
|
|
||||||
)
|
|
||||||
data = sco_pvpdf.pvjury_pdf(
|
|
||||||
dpv,
|
|
||||||
date_commission=date_commission,
|
date_commission=date_commission,
|
||||||
date_jury=date_jury,
|
date_jury=date_jury,
|
||||||
numero_arrete=numero_arrete,
|
numero_arrete=numero_arrete,
|
||||||
@ -410,7 +405,7 @@ def do_formsemestre_archive(
|
|||||||
anonymous=anonymous,
|
anonymous=anonymous,
|
||||||
)
|
)
|
||||||
if data:
|
if data:
|
||||||
PVArchive.store(archive_id, "PV_Jury%s.pdf" % groups_filename, data)
|
PVArchive.store(archive_id, f"PV_Jury{groups_filename}.pdf", data)
|
||||||
|
|
||||||
|
|
||||||
def formsemestre_archive(formsemestre_id, group_ids: list[int] = None):
|
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 = [
|
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>""",
|
</p>""",
|
||||||
html_sco_header.sco_footer(),
|
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"}),
|
("sep", {"input_type": "separator", "title": "Informations sur PV de jury"}),
|
||||||
]
|
]
|
||||||
descr += sco_pvjury.descrform_pvjury(formsemestre)
|
descr += sco_pv_forms.descrform_pvjury(formsemestre)
|
||||||
descr += [
|
descr += [
|
||||||
(
|
(
|
||||||
"signature",
|
"signature",
|
||||||
@ -507,7 +506,7 @@ enregistrés et non modifiables, on peut les retrouver ultérieurement.
|
|||||||
if tf[0] == 0:
|
if tf[0] == 0:
|
||||||
return "\n".join(H) + "\n" + tf[1] + "\n".join(F)
|
return "\n".join(H) + "\n" + tf[1] + "\n".join(F)
|
||||||
elif tf[0] == -1:
|
elif tf[0] == -1:
|
||||||
msg = "Opération%20annulée"
|
msg = "Opération annulée"
|
||||||
else:
|
else:
|
||||||
# submit
|
# submit
|
||||||
sf = tf[2]["signature"]
|
sf = tf[2]["signature"]
|
||||||
@ -531,7 +530,7 @@ enregistrés et non modifiables, on peut les retrouver ultérieurement.
|
|||||||
anonymous=tf[2]["anonymous"],
|
anonymous=tf[2]["anonymous"],
|
||||||
bul_version=tf[2]["bul_version"],
|
bul_version=tf[2]["bul_version"],
|
||||||
)
|
)
|
||||||
msg = "Nouvelle%20archive%20créée"
|
msg = "Nouvelle archive créée"
|
||||||
|
|
||||||
# submitted or cancelled:
|
# submitted or cancelled:
|
||||||
flash(msg)
|
flash(msg)
|
||||||
@ -546,7 +545,6 @@ enregistrés et non modifiables, on peut les retrouver ultérieurement.
|
|||||||
|
|
||||||
def formsemestre_list_archives(formsemestre_id):
|
def formsemestre_list_archives(formsemestre_id):
|
||||||
"""Page listing archives"""
|
"""Page listing archives"""
|
||||||
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
|
|
||||||
sem_archive_id = formsemestre_id
|
sem_archive_id = formsemestre_id
|
||||||
L = []
|
L = []
|
||||||
for archive_id in PVArchive.list_obj_archives(sem_archive_id):
|
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_formsemestre
|
||||||
from app.scodoc import sco_groups
|
from app.scodoc import sco_groups
|
||||||
from app.scodoc import sco_preferences
|
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
|
from app.scodoc import sco_users
|
||||||
import app.scodoc.sco_utils as scu
|
import app.scodoc.sco_utils as scu
|
||||||
from app.scodoc.sco_utils import ModuleType, fmt_note
|
from app.scodoc.sco_utils import ModuleType, fmt_note
|
||||||
@ -787,7 +787,7 @@ def etud_descr_situation_semestre(
|
|||||||
infos["date_defaillance"] = date_def
|
infos["date_defaillance"] = date_def
|
||||||
infos["descr_decision_jury"] = f"Défaillant{ne}"
|
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:
|
if dpv:
|
||||||
infos["decision_sem"] = dpv["decisions"][0]["decision_sem"]
|
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 sco_bac
|
||||||
from app.scodoc import codes_cursus
|
from app.scodoc import codes_cursus
|
||||||
from app.scodoc import sco_preferences
|
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
|
from app.scodoc import sco_etud
|
||||||
import sco_version
|
import sco_version
|
||||||
from app.scodoc.gen_tables import GenTable
|
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:
|
# Décisions de jury de tous les semestres:
|
||||||
dpv_by_sem = {}
|
dpv_by_sem = {}
|
||||||
for formsemestre_id in formsemestre_ids:
|
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
|
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)
|
formsemestre_ids = get_set_formsemestre_id_dates( start_date, end_date)
|
||||||
dpv_by_sem = {}
|
dpv_by_sem = {}
|
||||||
for formsemestre_id in formsemestre_ids:
|
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() ]
|
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.sco_cursus_dut import etud_est_inscrit_ue
|
||||||
from app.scodoc import sco_photos
|
from app.scodoc import sco_photos
|
||||||
from app.scodoc import sco_preferences
|
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(
|
def formsemestre_validation_etud_form(
|
||||||
@ -562,7 +562,7 @@ def formsemestre_recap_parcours_table(
|
|||||||
is_cur = Se.formsemestre_id == sem["formsemestre_id"]
|
is_cur = Se.formsemestre_id == sem["formsemestre_id"]
|
||||||
num_sem += 1
|
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]
|
pv = dpv["decisions"][0]
|
||||||
decision_sem = pv["decision_sem"]
|
decision_sem = pv["decision_sem"]
|
||||||
decisions_ue = pv["decisions_ue"]
|
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_formsemestre_inscriptions
|
||||||
from app.scodoc import sco_groups
|
from app.scodoc import sco_groups
|
||||||
from app.scodoc import sco_preferences
|
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
|
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]:
|
def list_etuds_from_sem(src, dst) -> list[dict]:
|
||||||
"""Liste des etudiants du semestre src qui sont autorisés à passer dans le semestre dst."""
|
"""Liste des etudiants du semestre src qui sont autorisés à passer dans le semestre dst."""
|
||||||
target = dst["semestre_id"]
|
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:
|
if not dpv:
|
||||||
return []
|
return []
|
||||||
etuds = [
|
etuds = [
|
||||||
|
@ -221,7 +221,7 @@ class ScoDocPageTemplate(PageTemplate):
|
|||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
document,
|
document,
|
||||||
pagesbookmarks={},
|
pagesbookmarks: dict = None,
|
||||||
author=None,
|
author=None,
|
||||||
title=None,
|
title=None,
|
||||||
subject=None,
|
subject=None,
|
||||||
@ -385,6 +385,11 @@ class BulletinDocTemplate(BaseDocTemplate):
|
|||||||
ajoute la gestion des bookmarks
|
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/
|
# inspired by https://www.reportlab.com/snippets/13/
|
||||||
def afterFlowable(self, flowable):
|
def afterFlowable(self, flowable):
|
||||||
"""Called by Reportlab after each flowable"""
|
"""Called by Reportlab after each flowable"""
|
||||||
|
@ -27,23 +27,7 @@
|
|||||||
|
|
||||||
"""Edition des PV de jury
|
"""Edition des PV de jury
|
||||||
|
|
||||||
PV Jury IUTV 2006: on détaillait 8 cas:
|
Formulaires paramétrage PV et génération des tables
|
||||||
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
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import time
|
import time
|
||||||
@ -54,25 +38,20 @@ import flask
|
|||||||
from flask import flash, redirect, url_for
|
from flask import flash, redirect, url_for
|
||||||
from flask import g, request
|
from flask import g, request
|
||||||
|
|
||||||
from app.models import (
|
from app.models import FormSemestre, Identite
|
||||||
Formation,
|
|
||||||
FormSemestre,
|
|
||||||
ScolarAutorisationInscription,
|
|
||||||
)
|
|
||||||
from app.models.etudiants import Identite
|
|
||||||
|
|
||||||
import app.scodoc.sco_utils as scu
|
import app.scodoc.sco_utils as scu
|
||||||
import app.scodoc.notesdb as ndb
|
import app.scodoc.notesdb as ndb
|
||||||
from app.scodoc import html_sco_header
|
from app.scodoc import html_sco_header
|
||||||
from app.scodoc import codes_cursus
|
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_etud
|
||||||
from app.scodoc import sco_formsemestre
|
|
||||||
from app.scodoc import sco_groups
|
from app.scodoc import sco_groups
|
||||||
from app.scodoc import sco_groups_view
|
from app.scodoc import sco_groups_view
|
||||||
from app.scodoc import sco_pdf
|
from app.scodoc import sco_pdf
|
||||||
from app.scodoc import sco_preferences
|
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.gen_tables import GenTable
|
||||||
from app.scodoc.codes_cursus import NO_SEMESTRE_ID
|
from app.scodoc.codes_cursus import NO_SEMESTRE_ID
|
||||||
from app.scodoc.sco_pdf import PDFLOCK
|
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()
|
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 not dpv:
|
||||||
if format == "html":
|
if format == "html":
|
||||||
return (
|
return (
|
||||||
@ -427,7 +406,7 @@ def formsemestre_pvjury_pdf(formsemestre_id, group_ids: list[int] = None, etudid
|
|||||||
tf[2]["anonymous"] = bool(tf[2]["anonymous"])
|
tf[2]["anonymous"] = bool(tf[2]["anonymous"])
|
||||||
try:
|
try:
|
||||||
PDFLOCK.acquire()
|
PDFLOCK.acquire()
|
||||||
pdfdoc = sco_pvpdf.pvjury_pdf(
|
pdfdoc = sco_pv_pdf.pvjury_pdf(
|
||||||
formsemestre,
|
formsemestre,
|
||||||
etudids,
|
etudids,
|
||||||
numero_arrete=tf[2]["numero_arrete"],
|
numero_arrete=tf[2]["numero_arrete"],
|
||||||
@ -596,7 +575,7 @@ def formsemestre_lettres_individuelles(formsemestre_id, group_ids=[]):
|
|||||||
signature = sf.read() # image of signature
|
signature = sf.read() # image of signature
|
||||||
try:
|
try:
|
||||||
PDFLOCK.acquire()
|
PDFLOCK.acquire()
|
||||||
pdfdoc = sco_pvpdf.pdf_lettres_individuelles(
|
pdfdoc = sco_pv_lettres_inviduelles.pdf_lettres_individuelles(
|
||||||
formsemestre_id,
|
formsemestre_id,
|
||||||
etudids=etudids,
|
etudids=etudids,
|
||||||
date_jury=tf[2]["date_jury"],
|
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
|
||||||
from app.scodoc import sco_formsemestre_inscriptions
|
from app.scodoc import sco_formsemestre_inscriptions
|
||||||
from app.scodoc import sco_preferences
|
from app.scodoc import sco_preferences
|
||||||
from app.scodoc import sco_pvjury
|
|
||||||
import sco_version
|
import sco_version
|
||||||
from app.scodoc.gen_tables import GenTable
|
from app.scodoc.gen_tables import GenTable
|
||||||
from app import log
|
from app import log
|
||||||
|
@ -15,6 +15,19 @@
|
|||||||
Information et documentation sur <a href="https://scodoc.org" target="_blank">scodoc.org</a>.
|
Information et documentation sur <a href="https://scodoc.org" target="_blank">scodoc.org</a>.
|
||||||
</p>
|
</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>
|
<h2>Dernières évolutions</h2>
|
||||||
|
|
||||||
{{ news|safe }}
|
{{ news|safe }}
|
||||||
|
@ -122,12 +122,11 @@ from app.scodoc import sco_lycee
|
|||||||
from app.scodoc import sco_moduleimpl
|
from app.scodoc import sco_moduleimpl
|
||||||
from app.scodoc import sco_moduleimpl_inscriptions
|
from app.scodoc import sco_moduleimpl_inscriptions
|
||||||
from app.scodoc import sco_moduleimpl_status
|
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_placement
|
||||||
from app.scodoc import sco_poursuite_dut
|
from app.scodoc import sco_poursuite_dut
|
||||||
from app.scodoc import sco_preferences
|
from app.scodoc import sco_preferences
|
||||||
from app.scodoc import sco_prepajury
|
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_recapcomplet
|
||||||
from app.scodoc import sco_report
|
from app.scodoc import sco_report
|
||||||
from app.scodoc import sco_report_but
|
from app.scodoc import sco_report_but
|
||||||
@ -2803,7 +2802,9 @@ def formsemestre_validation_suppress_etud(
|
|||||||
|
|
||||||
|
|
||||||
# ------------- PV de JURY et archives
|
# ------------- 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)
|
sco_publish("/pvjury_page_but", jury_but_pv.pvjury_page_but, Permission.ScoView)
|
||||||
|
|
||||||
@ -2913,12 +2914,12 @@ def formsemestre_jury_but_erase(
|
|||||||
|
|
||||||
sco_publish(
|
sco_publish(
|
||||||
"/formsemestre_lettres_individuelles",
|
"/formsemestre_lettres_individuelles",
|
||||||
sco_pvjury.formsemestre_lettres_individuelles,
|
sco_pv_forms.formsemestre_lettres_individuelles,
|
||||||
Permission.ScoView,
|
Permission.ScoView,
|
||||||
methods=["GET", "POST"],
|
methods=["GET", "POST"],
|
||||||
)
|
)
|
||||||
sco_publish(
|
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(
|
sco_publish(
|
||||||
"/feuille_preparation_jury",
|
"/feuille_preparation_jury",
|
||||||
|
@ -6,6 +6,15 @@ SCOVERSION = "9.4.45"
|
|||||||
SCONAME = "ScoDoc"
|
SCONAME = "ScoDoc"
|
||||||
|
|
||||||
SCONEWS = """
|
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>
|
<h4>Année 2022</h4>
|
||||||
<ul>
|
<ul>
|
||||||
<li>ScoDoc 9.4</li>
|
<li>ScoDoc 9.4</li>
|
||||||
@ -14,7 +23,7 @@ SCONEWS = """
|
|||||||
</ul>
|
</ul>
|
||||||
<li>ScoDoc 9.3</li>
|
<li>ScoDoc 9.3</li>
|
||||||
<ul>
|
<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>Module de gestion des relations avec les entreprises</li>
|
||||||
<li>Prise en charge des parcours BUT</li>
|
<li>Prise en charge des parcours BUT</li>
|
||||||
<li>Association des UEs aux compétences du référentiel</li>
|
<li>Association des UEs aux compétences du référentiel</li>
|
||||||
|
@ -29,7 +29,7 @@ from app.models import (
|
|||||||
UniteEns,
|
UniteEns,
|
||||||
)
|
)
|
||||||
from app.scodoc import sco_utils as scu
|
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):
|
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)
|
but_compare_decisions_annee(deca, deca_att)
|
||||||
if "autorisations_inscription" in doc_formsemestre["attendu"]:
|
if "autorisations_inscription" in doc_formsemestre["attendu"]:
|
||||||
if dpv is None: # lazy load
|
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(
|
check_autorisations_inscription(
|
||||||
etud, dpv, doc_formsemestre["attendu"]["autorisations_inscription"]
|
etud, dpv, doc_formsemestre["attendu"]["autorisations_inscription"]
|
||||||
)
|
)
|
||||||
|
Loading…
Reference in New Issue
Block a user