# -*- 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(scu.DATE_FMT) 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