ScoDoc-PE/app/scodoc/sco_bulletins_legacy.py

569 lines
21 KiB
Python

# -*- mode: python -*-
# -*- coding: utf-8 -*-
##############################################################################
#
# Gestion scolarite IUT
#
# Copyright (c) 1999 - 2024 Emmanuel Viennet. All rights reserved.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
# Emmanuel Viennet emmanuel.viennet@gmail.com
#
##############################################################################
"""Generation bulletins de notes dans l'ancien format de ScoDoc (avant juillet 2011).
Code partiellement redondant, copié de l'ancien système de gestion des bulletins.
Voir sco_bulletins_standard pour une version plus récente.
CE FORMAT N'EVOLUERA PLUS ET EST CONSIDERE COMME OBSOLETE.
"""
from flask import g, url_for
from reportlab.lib.colors import Color, blue
from reportlab.lib.units import cm, mm
from reportlab.platypus import Paragraph, Spacer, Table
from app.models import BulAppreciations
from app.scodoc import sco_bulletins_generator
from app.scodoc import sco_bulletins_pdf
from app.scodoc import sco_formsemestre
from app.scodoc.sco_permissions import Permission
from app.scodoc import sco_pdf
from app.scodoc.sco_pdf import SU
from app.scodoc import sco_preferences
import app.scodoc.sco_utils as scu
# Important: Le nom de la classe ne doit pas changer (bien le choisir), car il sera stocké en base de données (dans les préférences)
class BulletinGeneratorLegacy(sco_bulletins_generator.BulletinGenerator):
description = "Ancien format ScoDoc" # la description doit être courte: elle apparait dans le menu de paramètrage ScoDoc
supported_formats = ["html", "pdf"]
def bul_title_pdf(self):
"""Génère la partie "titre" du bulletin de notes.
Renvoie une liste d'objets platypus
"""
objects = sco_bulletins_pdf.process_field(
self.preferences["bul_pdf_title"], self.infos, self.style_field
)
objects.append(
Spacer(1, 5 * mm)
) # impose un espace vertical entre le titre et la table qui suit
return objects
def bul_table(self, fmt="html"):
"""Table bulletin"""
if fmt == "pdf":
return self.bul_table_pdf()
elif fmt == "html":
return self.bul_table_html()
else:
raise ValueError(f"invalid bulletin format ({fmt})")
def bul_table_pdf(self):
"""Génère la table centrale du bulletin de notes
Renvoie une liste d'objets PLATYPUS (eg instance de Table).
"""
P, pdfTableStyle, colWidths = _bulletin_pdf_table_legacy(
self.infos, version=self.version
)
return [self.buildTableObject(P, pdfTableStyle, colWidths)]
def bul_table_html(self):
"""Génère la table centrale du bulletin de notes: chaine HTML"""
I = self.infos
formsemestre_id = self.infos["formsemestre_id"]
bul_show_abs_modules = sco_preferences.get_preference(
"bul_show_abs_modules", formsemestre_id
)
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
if sem["bul_bgcolor"]:
bgcolor = sem["bul_bgcolor"]
else:
bgcolor = "background-color: rgb(255,255,240)"
linktmpl = '<span onclick="toggle_vis_ue(this);" class="toggle_ue">%s</span>'
minuslink = linktmpl % scu.icontag("minus_img", border="0", alt="-")
pluslink = linktmpl % scu.icontag("plus_img", border="0", alt="+")
H = ['<table class="notes_bulletin" style="background-color: %s;">' % bgcolor]
if sco_preferences.get_preference("bul_show_minmax", formsemestre_id):
minmax = (
'<span class="bul_minmax" title="[min, max] promo">[%s, %s]</span>'
% (I["moy_min"], I["moy_max"])
)
bargraph = ""
else:
minmax = ""
bargraph = I["moy_gen_bargraph_html"]
# 1ere ligne: titres
H.append(
'<tr><td class="note_bold">Moyenne</td><td class="note_bold cell_graph">%s%s%s%s</td>'
% (I["moy_gen"], I["etud_etat_html"], minmax, bargraph)
)
H.append('<td class="note_bold">%s</td>' % I["rang_txt"])
H.append('<td class="note_bold">Note/20</td><td class="note_bold">Coef</td>')
if bul_show_abs_modules:
H.append('<td class="note_bold">Abs (J. / N.J.)</td>')
H.append("</tr>")
def list_modules(ue_modules, rowstyle):
for mod in ue_modules:
if mod["mod_moy_txt"] == "NI":
continue # saute les modules où on n'est pas inscrit
H.append('<tr class="notes_bulletin_row_mod%s">' % rowstyle)
if sco_preferences.get_preference(
"bul_show_minmax_mod", formsemestre_id
):
rang_minmax = (
'%s <span class="bul_minmax" title="[min, max] UE">[%s, %s]</span>'
% (
mod["mod_rang_txt"],
scu.fmt_note(mod["stats"]["min"]),
scu.fmt_note(mod["stats"]["max"]),
)
)
else:
rang_minmax = mod["mod_rang_txt"] # vide si pas option rang
H.append(
'<td>%s</td><td>%s</td><td>%s</td><td class="note">%s</td><td>%s</td>'
% (
mod["code_html"],
mod["name_html"],
rang_minmax,
mod["mod_moy_txt"],
mod["mod_coef_txt"],
)
)
if bul_show_abs_modules:
H.append("<td>%s</td>" % mod["mod_abs_txt"])
H.append("</tr>")
if self.version != "short":
# --- notes de chaque eval:
for e in mod["evaluations"]:
if e["visibulletin"] or self.version == "long":
H.append(
'<tr class="notes_bulletin_row_eval%s">' % rowstyle
)
H.append(
'<td>%s</td><td>%s</td><td class="bull_nom_eval">%s</td><td class="note">%s</td><td class="bull_coef_eval">%s</td></tr>'
% (
"",
"",
e["name_html"],
e["note_html"],
e["coef_txt"],
)
)
# Contenu table: UE après UE
for ue in I["ues"]:
ue_descr = ue["ue_descr_html"]
coef_ue = ue["coef_ue_txt"]
rowstyle = ""
plusminus = minuslink #
if ue["ue_status"]["is_capitalized"]:
if sco_preferences.get_preference(
"bul_show_ue_cap_details", formsemestre_id
):
plusminus = minuslink
hide = ""
else:
plusminus = pluslink
hide = "sco_hide"
H.append('<tr class="notes_bulletin_row_ue">')
H.append(
'<td class="note_bold">%s%s</td><td class="note_bold">%s</td><td>%s</td><td>%s</td><td>%s</td>'
% (
plusminus,
ue["acronyme"],
ue["moy_ue_txt"],
ue_descr,
"",
coef_ue,
)
)
if bul_show_abs_modules:
H.append("<td></td>")
H.append("</tr>")
list_modules(ue["modules_capitalized"], " bul_row_ue_cap %s" % hide)
coef_ue = ""
ue_descr = "(en cours, non prise en compte)"
rowstyle = (
" bul_row_ue_cur" # style css pour indiquer UE non prise en compte
)
H.append('<tr class="notes_bulletin_row_ue">')
if sco_preferences.get_preference("bul_show_minmax", formsemestre_id):
moy_txt = (
'%s <span class="bul_minmax" title="[min, max] UE">[%s, %s]</span>'
% (
scu.fmt_note(ue["cur_moy_ue_txt"]),
scu.fmt_note(ue["min"]),
scu.fmt_note(ue["max"]),
)
)
else:
moy_txt = ue["cur_moy_ue_txt"]
H.append(
'<td class="note_bold">%s%s</td><td class="note_bold">%s</td><td>%s</td><td>%s</td><td>%s</td>'
% (minuslink, ue["acronyme"], moy_txt, ue_descr, "", coef_ue)
)
if bul_show_abs_modules:
H.append("<td></td>")
H.append("</tr>")
list_modules(ue["modules"], rowstyle)
H.append("</table>")
# ---------------
return "\n".join(H)
def bul_part_below(self, fmt="html"):
"""Génère les informations placées sous la table de notes
(absences, appréciations, décisions de jury...)
"""
if fmt == "pdf":
return self.bul_part_below_pdf()
elif fmt == "html":
return self.bul_part_below_html()
else:
raise ValueError(f"invalid bulletin format ({fmt})")
def bul_part_below_pdf(self):
"""
Renvoie une liste d'objets platypus
"""
objects = []
# ----- ABSENCES
if self.preferences["bul_show_abs"]:
objects.append(Spacer(1, 2 * mm))
if self.infos["nbabs"]:
metrique = scu.AssiduitesMetrics.short_to_str(
self.preferences["assi_metrique"], lower_plural=True
)
objects.append(
Paragraph(
SU(
f"""{self.infos['nbabs']:g} {metrique} d'absences, dont {
self.infos['nbabsjust']:g} justifiées."""
),
self.CellStyle,
)
)
else:
objects.append(
Paragraph(SU("Pas d'absences signalées."), self.CellStyle)
)
# ----- APPRECIATIONS
appreciations = BulAppreciations.get_appreciations_list(
self.formsemestre.id, self.etud.id
)
if appreciations:
objects.append(Spacer(1, 3 * mm))
objects.append(
Paragraph(
SU(
"Appréciation : "
+ "\n".join(BulAppreciations.summarize(appreciations))
),
self.CellStyle,
)
)
# ----- DECISION JURY
if self.preferences["bul_show_decision"]:
objects += sco_bulletins_pdf.process_field(
self.preferences["bul_pdf_caption"],
self.infos,
self.style_field,
)
return objects
def bul_part_below_html(self):
"""
Renvoie chaine HTML
"""
I = self.infos
authuser = self.authuser
H = []
# --- Absences
H.append(
f"""<p>
<a href="{ url_for('assiduites.calendrier_assi_etud',
scodoc_dept=g.scodoc_dept, etudid=I['etudid']) }" class="bull_link">
<b>Absences :</b>{I['nbabs']} demi-journées, dont {I['nbabsjust']} justifiées
(pendant ce semestre).
</a></p>
"""
)
# --- Decision Jury
if I["situation"]:
H.append("""<p class="bull_situation">%(situation)s</p>""" % I)
# --- Appreciations
# le dir. des etud peut ajouter des appreciations,
# mais aussi le chef (perm. EtudInscrit)
can_edit_app = (authuser.id in self.infos["responsables"]) or (
authuser.has_permission(Permission.EtudInscrit)
)
H.append('<div class="bull_appreciations">')
appreciations = BulAppreciations.get_appreciations_list(
self.formsemestre.id, self.etud.id
)
if appreciations:
H.append("<p><b>Appréciations</b></p>")
for appreciation in appreciations:
if can_edit_app:
mlink = f"""<a class="stdlink" href="{
url_for('notes.appreciation_add_form', scodoc_dept=g.scodoc_dept, appreciation_id=appreciation.id)
}">modifier</a>
<a class="stdlink" href="{
url_for('notes.appreciation_add_form', scodoc_dept=g.scodoc_dept, appreciation_id=appreciation.id, suppress=1)
}">supprimer</a>"""
else:
mlink = ""
H.append(
f"""<p>
<span class="bull_appreciations_date">{
appreciation.date.strftime("%d/%m/%y") if appreciation.date else ""
}</span>
{appreciation.comment_safe()}
<span class="bull_appreciations_link">{mlink}</span>
</p>"""
)
if can_edit_app:
H.append(
f"""<p>
<a class="stdlink" href="{
url_for('notes.appreciation_add_form',
scodoc_dept=g.scodoc_dept,
etudid=self.etud.id,
formsemestre_id=self.formsemestre.id)
}">Ajouter une appréciation</a>
</p>"""
)
H.append("</div>")
# ---------------
return "\n".join(H)
def bul_signatures_pdf(self):
"""Génère les signatures placées en bas du bulletin
Renvoie une liste d'objets platypus
"""
show_left = self.preferences["bul_show_sig_left"]
show_right = self.preferences["bul_show_sig_right"]
if show_left or show_right:
if show_left:
L = [
[
sco_bulletins_pdf.process_field(
self.preferences["bul_pdf_sig_left"],
self.infos,
self.style_field,
)
]
]
else:
L = [[""]]
if show_right:
L[0].append(
sco_bulletins_pdf.process_field(
self.preferences["bul_pdf_sig_right"],
self.infos,
self.style_field,
)
)
else:
L[0].append("")
t = Table(L)
t._argW[0] = 10 * cm # fixe largeur colonne gauche
return [Spacer(1, 1.5 * cm), t] # espace vertical avant signatures
else:
return []
# sco_bulletins_generator.register_bulletin_class(BulletinGeneratorLegacy)
class BulTableStyle(object):
"""Construction du style de tables reportlab platypus pour les bulletins "classiques" """
LINEWIDTH = 0.5
LINECOLOR = Color(0, 0, 0)
UEBGCOLOR = Color(
170 / 255.0, 187 / 255.0, 204 / 255.0
) # couleur fond lignes titres UE
MODSEPCOLOR = Color(
170 / 255.0, 170 / 255.0, 170 / 255.0
) # lignes séparant les modules
def __init__(self):
self.pdfTableStyle = [
("LINEBELOW", (0, 0), (-1, 0), self.LINEWIDTH, self.LINECOLOR)
]
self.tabline = 0
def get_style(self):
"get resulting style (a list of platypus table commands)"
# ajoute cadre extérieur bleu:
self.pdfTableStyle.append(("BOX", (0, 0), (-1, -1), 0.4, blue))
return self.pdfTableStyle
def newline(self, ue_type=None):
self.tabline += 1
if ue_type == "cur": # UE courante non prise en compte (car capitalisee)
self.pdfTableStyle.append(
(
"BACKGROUND",
(0, self.tabline),
(-1, self.tabline),
Color(210 / 255.0, 210 / 255.0, 210 / 255.0),
)
)
def ueline(self): # met la ligne courante du tableau pdf en style 'UE'
self.newline()
i = self.tabline
self.pdfTableStyle.append(("BACKGROUND", (0, i), (-1, i), self.UEBGCOLOR))
def modline(
self, ue_type=None
): # met la ligne courante du tableau pdf en style 'Module'
self.newline(ue_type=ue_type)
i = self.tabline
self.pdfTableStyle.append(("LINEABOVE", (0, i), (-1, i), 1, self.MODSEPCOLOR))
def _bulletin_pdf_table_legacy(I, version="long"):
"""Génère la table centrale du bulletin de notes
Renvoie un triplet:
- table (liste de listes de chaines de caracteres)
- style (commandes table Platypus)
- largeurs de colonnes
"""
S = BulTableStyle()
P = [] # elems pour gen. pdf
formsemestre_id = I["formsemestre_id"]
bul_show_abs_modules = sco_preferences.get_preference(
"bul_show_abs_modules", formsemestre_id
)
if sco_preferences.get_preference("bul_show_minmax", formsemestre_id):
minmax = ' <font size="8">[%s, %s]</font>' % (I["moy_min"], I["moy_max"])
else:
minmax = ""
t = [
"Moyenne",
"%s%s%s" % (I["moy_gen"], I["etud_etat_html"], minmax),
I["rang_txt"],
"Note/20",
"Coef",
]
if bul_show_abs_modules:
t.append("Abs (J. / N.J.)")
P.append(sco_pdf.bold_paras(t))
def list_modules(ue_modules, ue_type=None):
"ajoute les lignes decrivant les modules d'une UE, avec eventuellement les évaluations de chacun"
for mod in ue_modules:
if mod["mod_moy_txt"] == "NI":
continue # saute les modules où on n'est pas inscrit
S.modline(ue_type=ue_type)
if sco_preferences.get_preference("bul_show_minmax_mod", formsemestre_id):
rang_minmax = '%s <font size="8">[%s, %s]</font>' % (
mod["mod_rang_txt"],
scu.fmt_note(mod["stats"]["min"]),
scu.fmt_note(mod["stats"]["max"]),
)
else:
rang_minmax = mod["mod_rang_txt"] # vide si pas option rang
t = [
mod["code"] or "",
mod["name"] or "",
rang_minmax,
mod["mod_moy_txt"],
mod["mod_coef_txt"],
]
if bul_show_abs_modules:
t.append(mod["mod_abs_txt"])
P.append(t)
if version != "short":
# --- notes de chaque eval:
for e in mod["evaluations"]:
if e["visibulletin"] or version == "long":
S.newline(ue_type=ue_type)
t = ["", "", e["name"], e["note_txt"], e["coef_txt"]]
if bul_show_abs_modules:
t.append("")
P.append(t)
for ue in I["ues"]:
ue_descr = ue["ue_descr_txt"]
coef_ue = ue["coef_ue_txt"]
ue_type = None
if ue["ue_status"]["is_capitalized"]:
t = [ue["acronyme"], ue["moy_ue_txt"], ue_descr, "", coef_ue]
if bul_show_abs_modules:
t.append("")
P.append(sco_pdf.bold_paras(t))
coef_ue = ""
ue_descr = "(en cours, non prise en compte)"
S.ueline()
if sco_preferences.get_preference(
"bul_show_ue_cap_details", formsemestre_id
):
list_modules(ue["modules_capitalized"])
ue_type = "cur"
if sco_preferences.get_preference("bul_show_minmax", formsemestre_id):
moy_txt = '%s <font size="8">[%s, %s]</font>' % (
ue["cur_moy_ue_txt"],
ue["min"],
ue["max"],
)
else:
moy_txt = ue["cur_moy_ue_txt"]
t = [ue["acronyme"], moy_txt, ue_descr, "", coef_ue]
if bul_show_abs_modules:
t.append("")
P.append(sco_pdf.bold_paras(t))
S.ueline()
list_modules(ue["modules"], ue_type=ue_type)
# Largeur colonnes:
colWidths = [None, 5 * cm, 6 * cm, 2 * cm, 1.2 * cm]
if len(P[0]) > 5:
colWidths.append(1.5 * cm) # absences/modules
return P, S.get_style(), colWidths