forked from ScoDoc/ScoDoc
569 lines
21 KiB
Python
569 lines
21 KiB
Python
# -*- mode: python -*-
|
|
# -*- coding: utf-8 -*-
|
|
|
|
##############################################################################
|
|
#
|
|
# Gestion scolarite IUT
|
|
#
|
|
# Copyright (c) 1999 - 2023 Emmanuel Viennet. All rights reserved.
|
|
#
|
|
# This program is free software; you can redistribute it and/or modify
|
|
# it under the terms of the GNU General Public License as published by
|
|
# the Free Software Foundation; either version 2 of the License, or
|
|
# (at your option) any later version.
|
|
#
|
|
# This program is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License
|
|
# along with this program; if not, write to the Free Software
|
|
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
#
|
|
# Emmanuel Viennet emmanuel.viennet@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
|