diff --git a/app/__init__.py b/app/__init__.py index f1fba060a..cf1495be3 100755 --- a/app/__init__.py +++ b/app/__init__.py @@ -34,9 +34,9 @@ from flask_sqlalchemy import SQLAlchemy from jinja2 import select_autoescape import sqlalchemy as sa +import werkzeug.debug from flask_cas import CAS -import werkzeug.debug from app.scodoc.sco_exceptions import ( AccessDenied, @@ -44,6 +44,7 @@ from app.scodoc.sco_exceptions import ( ScoException, ScoGenError, ScoInvalidCSRF, + ScoPDFFormatError, ScoValueError, APIInvalidParams, ) @@ -74,6 +75,7 @@ cache = Cache( def handle_sco_value_error(exc): + "page d'erreur avec message" return render_template("sco_value_error.j2", exc=exc), 404 @@ -90,6 +92,10 @@ def handle_invalid_csrf(exc): return render_template("error_csrf.j2", exc=exc), 404 +def handle_pdf_format_error(exc): + return "ay ay ay" + + def internal_server_error(exc): """Bugs scodoc, erreurs 500""" # note that we set the 500 status explicitly @@ -310,6 +316,7 @@ def create_app(config_class=DevConfig): app.register_error_handler(ScoValueError, handle_sco_value_error) app.register_error_handler(ScoBugCatcher, handle_sco_bug) app.register_error_handler(ScoInvalidCSRF, handle_invalid_csrf) + app.register_error_handler(ScoPDFFormatError, handle_pdf_format_error) app.register_error_handler(AccessDenied, handle_access_denied) app.register_error_handler(500, internal_server_error) app.register_error_handler(503, postgresql_server_error) diff --git a/app/but/bulletin_but.py b/app/but/bulletin_but.py index 74d167eba..4cf2e88c2 100644 --- a/app/but/bulletin_but.py +++ b/app/but/bulletin_but.py @@ -333,7 +333,6 @@ class BulletinBUT: def bulletin_etud( self, etud: Identite, - formsemestre: FormSemestre, force_publishing=False, version="long", ) -> dict: @@ -346,6 +345,7 @@ class BulletinBUT: (bulletins non publiés). """ res = self.res + formsemestre = res.formsemestre etat_inscription = etud.inscription_etat(formsemestre.id) nb_inscrits = self.res.get_inscriptions_counts()[scu.INSCRIT] published = (not formsemestre.bul_hide_xml) or force_publishing @@ -489,9 +489,7 @@ class BulletinBUT: (pas utilisé pour json/html) Résultat compatible avec celui de sco_bulletins.formsemestre_bulletinetud_dict """ - d = self.bulletin_etud( - etud, self.res.formsemestre, version=version, force_publishing=True - ) + d = self.bulletin_etud(etud, version=version, force_publishing=True) d["etudid"] = etud.id d["etud"] = d["etudiant"] d["etud"]["nomprenom"] = etud.nomprenom diff --git a/app/but/bulletin_but_court.py b/app/but/bulletin_but_court.py index 0396b1bc5..4ba1aee9a 100644 --- a/app/but/bulletin_but_court.py +++ b/app/but/bulletin_but_court.py @@ -25,8 +25,8 @@ Ces données sont des objets passés au template. import datetime import time -from flask import render_template, url_for -from flask import g, request +from flask import render_template +from flask import g from app.but.bulletin_but import BulletinBUT from app.but import bulletin_but_court_pdf, cursus_but, validations_view @@ -35,6 +35,7 @@ from app.decorators import ( permission_required, ) from app.models import FormSemestre, FormSemestreInscription, Identite +from app.scodoc.codes_cursus import UE_STANDARD from app.scodoc.sco_exceptions import ScoNoReferentielCompetences from app.scodoc.sco_logos import find_logo from app.scodoc.sco_permissions import Permission @@ -59,7 +60,10 @@ def bulletin_but(formsemestre_id: int, etudid: int = None, fmt="html"): .first_or_404() ) bulletins_sem = BulletinBUT(formsemestre) - bul = bulletins_sem.bulletin_etud(etud, formsemestre) # dict + if fmt == "pdf": + bul: dict = bulletins_sem.bulletin_etud_complet(etud) + else: # la même chose avec un peu moins d'infos + bul: dict = bulletins_sem.bulletin_etud(etud) decision_ues = {x["acronyme"]: x for x in bul["semestre"]["decision_ue"]} cursus = cursus_but.EtudCursusBUT(etud, formsemestre.formation) refcomp = formsemestre.formation.referentiel_competence @@ -82,8 +86,10 @@ def bulletin_but(formsemestre_id: int, etudid: int = None, fmt="html"): "logo": logo, "title": f"Bul. {etud.nom_disp()} BUT (court)", "ue_validation_by_niveau": ue_validation_by_niveau, + "ues_acronyms": [ + ue.acronyme for ue in bulletins_sem.res.ues if ue.type == UE_STANDARD + ], } - if fmt == "pdf": filename = scu.bul_filename(formsemestre, etud, prefix="bul-but") bul_pdf = bulletin_but_court_pdf.make_bulletin_but_court_pdf(**args) diff --git a/app/but/bulletin_but_court_pdf.py b/app/but/bulletin_but_court_pdf.py index 173630c18..3f0a1fb65 100644 --- a/app/but/bulletin_but_court_pdf.py +++ b/app/but/bulletin_but_court_pdf.py @@ -10,19 +10,23 @@ On génère du PDF avec reportLab en utilisant les classes ScoDoc BulletinGenerator et GenTable. """ +import datetime from flask_login import current_user -from reportlab.lib.colors import blue +from reportlab.lib import styles +from reportlab.lib.colors import black, white, Color +from reportlab.lib.enums import TA_CENTER from reportlab.lib.units import cm, mm -from reportlab.platypus import Paragraph, Spacer +from reportlab.platypus import Paragraph, Spacer, Table from app.but import cursus_but from app.models import FormSemestre, Identite, ScolarFormSemestreValidation from app.scodoc.sco_bulletins_standard import BulletinGeneratorStandard +from app.scodoc import sco_bulletins from app.scodoc.sco_logos import Logo -from app.scodoc import gen_tables, sco_pdf, sco_preferences -from app.scodoc.sco_pdf import PDFLOCK +from app.scodoc import sco_pdf, sco_preferences +from app.scodoc.sco_pdf import PDFLOCK, SU def make_bulletin_but_court_pdf( @@ -35,6 +39,7 @@ def make_bulletin_but_court_pdf( logo: Logo = None, title: str = "", ue_validation_by_niveau: dict[tuple[int, str], ScolarFormSemestreValidation] = None, + ues_acronyms: list[str] = None, ) -> bytes: # A priori ce verrou n'est plus nécessaire avec Flask (multi-process) # mais... @@ -58,6 +63,7 @@ class BulletinGeneratorBUTCourt(BulletinGeneratorStandard): scale_table_in_page = True # pas de mise à l'échelle pleine page auto multi_pages = False # une page par bulletin small_fontsize = "8" + color_blue_bg = Color(0, 153 / 255, 204 / 255) def __init__( self, @@ -72,16 +78,9 @@ class BulletinGeneratorBUTCourt(BulletinGeneratorStandard): ue_validation_by_niveau: dict[ tuple[int, str], ScolarFormSemestreValidation ] = None, + ues_acronyms: list[str] = None, ): - # données pour anciens codes bulletins... à moderniser - infos = { - "etud": etud.to_dict_bul(), - "filigranne": None, - "formsemestre_id": formsemestre.id, - "nbabs": 0, - "nbabsjust": 0, - } - super().__init__(infos, authuser=current_user) + super().__init__(bul, authuser=current_user) self.bul = bul self.cursus = cursus self.decision_ues = decision_ues @@ -91,32 +90,334 @@ class BulletinGeneratorBUTCourt(BulletinGeneratorStandard): self.logo = logo self.title = title self.ue_validation_by_niveau = ue_validation_by_niveau + self.ues_acronyms = ues_acronyms # sans UEs sport - def bul_table(self, fmt=None): + self.nb_ues = len(self.ues_acronyms) + # Styles PDF + self.style_cell = styles.ParagraphStyle("style_cell") + self.style_cell.fontName = "Helvetica" + self.style_cell.fontSize = 7 + self.style_cell.leading = 7 + self.style_bold = styles.ParagraphStyle("style_bold", self.style_cell) + self.style_bold.fontName = "Helvetica-Bold" + self.style_head = styles.ParagraphStyle("style_head", self.style_bold) + self.style_head.fontSize = 9 + + self.style_niveaux = styles.ParagraphStyle("style_niveaux", self.style_cell) + self.style_niveaux.alignment = TA_CENTER + self.style_niveaux.leading = 9 + self.style_niveaux.firstLineIndent = 0 + self.style_niveaux.leftIndent = 1 + self.style_niveaux.rightIndent = 1 + self.style_niveaux.borderWidth = 1 + self.style_niveaux.borderPadding = 2 + self.style_niveaux.borderRadius = 2 + self.style_niveaux_top = styles.ParagraphStyle( + "style_niveaux_top", self.style_niveaux + ) + self.style_niveaux_top.fontName = "Helvetica-Bold" + self.style_niveaux_top.fontSize = 8 + self.style_niveaux_titre = styles.ParagraphStyle( + "style_niveaux_titre", self.style_niveaux + ) + self.style_niveaux_titre.textColor = white + self.style_niveaux_titre.backColor = self.color_blue_bg + self.style_niveaux_titre.borderColor = self.color_blue_bg + + self.style_niveaux_code = styles.ParagraphStyle( + "style_niveaux_code", self.style_niveaux + ) + self.style_niveaux_code.borderColor = black + + # Géométrie page + self.width_page_avail = 185 * mm # largeur utilisable + # Géométrie tableaux + self.width_col_ue = 18 * mm + self.width_col_ue_titres = 15 * mm + # Modules + self.width_col_code = self.width_col_ue + # Niveaux + self.width_col_niveaux_titre = 24 * mm + self.width_col_niveaux_code = 12 * mm + + def bul_table(self, fmt=None) -> list: """Génère la table centrale du bulletin de notes Renvoie: une liste d'objets PLATYPUS (eg instance de Table). L'argument fmt est ici ignoré (toujours en PDF) """ - ue_table = self.build_ue_table() - - return ue_table.gen(format="pdf") - - def build_ue_table(self) -> gen_tables.GenTable: - """Table avec les résultats d'UE du semestre courant""" - columns_ids = ("titre", "UE1", "UE2") - rows = [ - {"titre": "ligne 1", "UE1": "12.", "UE2": "13"}, - {"titre": "ligne 2", "UE1": "14.", "UE2": "15"}, + style_table_2cols = [ + ("ALIGN", (0, -1), (0, -1), "LEFT"), + ("ALIGN", (-1, -1), (-1, -1), "RIGHT"), + ("VALIGN", (0, 0), (-1, 1), "TOP"), + ("LEFTPADDING", (0, 0), (-1, -1), 0), + ("TOPPADDING", (0, 0), (-1, -1), 0), + ("RIGHTPADDING", (0, 0), (-1, -1), 0), + ("BOTTOMPADDING", (0, 0), (-1, -1), 0), ] - pdf_style = [ - ("VALIGN", (0, 0), (-1, -1), "TOP"), - ("BOX", (0, 0), (-1, -1), 0.4, blue), # ajoute cadre extérieur bleu - ] - col_widths = {"titre": 3 * cm, "UE1": 1 * cm, "UE2": 1 * cm} - return gen_tables.GenTable( - rows=rows, - columns_ids=columns_ids, - pdf_table_style=pdf_style, - pdf_col_widths=[col_widths[k] for k in columns_ids], - preferences=sco_preferences.SemPreferences(self.formsemestre.id), + # Ligne avec boite assiduité et table UEs + table_abs_ues = Table( + [[self.box_assiduite(), self.table_ues()]], + colWidths=(3 * cm, self.width_page_avail - 3 * cm), + style=style_table_2cols, ) + table_abs_ues.hAlign = "RIGHT" + # Ligne (en bas) avec table cursus et boite jury + table_cursus_jury = Table( + [[self.table_cursus_but(), self.boite_decisions_jury()]], + colWidths=(self.width_page_avail - 45 * mm, 45 * mm), + style=style_table_2cols, + ) + return [ + table_abs_ues, + Spacer(0, 3 * mm), + self.table_ressources(), + Spacer(0, 3 * mm), + self.table_saes(), + Spacer(0, 5 * mm), + table_cursus_jury, + ] + + def table_ues(self) -> Table: + """Table avec les résultats d'UE du semestre courant""" + bul = self.bul + rows = [ + [ + f"Unités d'enseignement du semestre {self.formsemestre.semestre_id}", + ], + [""] + self.ues_acronyms, + ["Moyenne"] + + [bul["ues"][ue]["moyenne"]["value"] for ue in self.ues_acronyms], + ["Bonus"] + + [ + bul["ues"][ue]["bonus"] if bul["ues"][ue]["bonus"] != "00.00" else "" + for ue in self.ues_acronyms + ], + ["Malus"] + + [ + bul["ues"][ue]["malus"] if bul["ues"][ue]["malus"] != "00.00" else "" + for ue in self.ues_acronyms + ], + ["Rang"] + [bul["ues"][ue]["moyenne"]["rang"] for ue in self.ues_acronyms], + ["Effectif"] + + [bul["ues"][ue]["moyenne"]["total"] for ue in self.ues_acronyms], + ["ECTS"] + + [ + f'{self.decision_ues[ue]["ects"]:g}' if ue in self.decision_ues else "" + for ue in self.ues_acronyms + ], + ["Jury"] + + [ + self.decision_ues[ue]["code"] if ue in self.decision_ues else "" + for ue in self.ues_acronyms + ], + ] + blue_bg = Color(183 / 255.0, 235 / 255.0, 255 / 255.0) + table_style = [ + ("VALIGN", (0, 0), (-1, -1), "TOP"), + ("BOX", (0, 0), (-1, -1), 1.0, black), # ajoute cadre extérieur + ("INNERGRID", (0, 0), (-1, -1), 0.25, black), + ("LEADING", (0, 1), (-1, -1), 5), + ("SPAN", (0, 0), (self.nb_ues, 0)), + ("BACKGROUND", (0, 0), (self.nb_ues, 0), blue_bg), + ] + col_widths = [self.width_col_ue_titres] + [self.width_col_ue] * self.nb_ues + + rows_styled = [[Paragraph(SU(str(cell)), self.style_head) for cell in rows[0]]] + rows_styled += [[Paragraph(SU(str(cell)), self.style_bold) for cell in rows[1]]] + rows_styled += [ + [Paragraph(SU(str(cell)), self.style_cell) for cell in row] + for row in rows[2:-1] + ] + rows_styled += [ + [Paragraph(SU(str(cell)), self.style_bold) for cell in rows[-1]] + ] + table = Table( + rows_styled, + colWidths=col_widths, + style=table_style, + ) + table.hAlign = "RIGHT" + return table + + def _table_modules(self, mod_type: str = "ressources", title: str = "") -> Table: + "génère table des modules: resources ou SAEs" + bul = self.bul + rows = [ + ["", "", "Unités d'enseignement"] + [""] * (self.nb_ues - 1), + [title, ""] + self.ues_acronyms, + ] + for mod in self.bul[mod_type]: + row = [mod, bul[mod_type][mod]["titre"]] + row += [ + bul["ues"][ue][mod_type][mod]["moyenne"] + if mod in bul["ues"][ue][mod_type] + else "" + for ue in self.ues_acronyms + ] + rows.append(row) + + title_bg = ( + Color(255 / 255, 192 / 255, 0) + if mod_type == "ressources" + else Color(176 / 255, 255 / 255, 99 / 255) + ) + + table_style = [ + ("VALIGN", (0, 0), (-1, -1), "TOP"), + ("BOX", (0, 0), (-1, -1), 1.0, black), # ajoute cadre extérieur + ("INNERGRID", (0, 0), (-1, -1), 0.25, black), + ("LEADING", (0, 1), (-1, -1), 5), + # 1ère ligne titre + ("SPAN", (0, 0), (1, 0)), + ("SPAN", (2, 0), (self.nb_ues, 0)), + # 2ème ligne titre + ("SPAN", (0, 1), (1, 1)), + ("BACKGROUND", (0, 1), (1, 1), title_bg), + ] + # Estime l'espace horizontal restant pour les titres de modules + width_col_titre_module = ( + self.width_page_avail + - self.width_col_code + - self.width_col_ue * self.nb_ues + ) + col_widths = [self.width_col_code, width_col_titre_module] + [ + self.width_col_ue + ] * self.nb_ues + + rows_styled = [ + [Paragraph(SU(str(cell)), self.style_bold) for cell in row] + for row in rows[:2] + ] + rows_styled += [ + [Paragraph(SU(str(cell)), self.style_cell) for cell in row] + for row in rows[2:] + ] + table = Table( + rows_styled, + colWidths=col_widths, + style=table_style, + ) + table.hAlign = "RIGHT" + return table + + def table_ressources(self) -> Table: + "La table des ressources" + return self._table_modules("ressources", "Ressources") + + def table_saes(self) -> Table: + "La table des SAEs" + return self._table_modules( + "saes", "Situations d'Apprentissage et d'Évaluation (SAÉ)" + ) + + def box_assiduite(self) -> Table: + "Les informations sur l'assiduité" + if not self.bul["options"]["show_abs"]: + return Paragraph("") # empty + color_bg = Color(245 / 255, 237 / 255, 200 / 255) + rows = [ + ["Absences", ""], + [f'({self.bul["semestre"]["absences"]["metrique"]})', ""], + ["Non justifiées", self.bul["semestre"]["absences"]["injustifie"]], + ["Total", self.bul["semestre"]["absences"]["total"]], + ] + rows_styled = [ + [Paragraph(SU(str(cell)), self.style_head) for cell in row] + for row in rows[:1] + ] + rows_styled += [ + [Paragraph(SU(str(cell)), self.style_cell) for cell in row] + for row in rows[1:] + ] + table = Table( + rows_styled, + # [topLeft, topRight, bottomLeft bottomRight] + cornerRadii=[2 * mm] * 4, + style=[ + ("BACKGROUND", (0, 0), (-1, -1), color_bg), + ("SPAN", (0, 0), (1, 0)), + ("SPAN", (0, 1), (1, 1)), + ("VALIGN", (0, 0), (-1, -1), "TOP"), + ], + ) + table.hAlign = "LEFT" + return table + + def table_cursus_but(self) -> Table: + "La table avec niveaux et validations BUT1, BUT2, BUT3" + rows = [ + ["", "BUT 1", "BUT 2", "BUT 3"], + ] + for competence_id in self.cursus.to_dict(): + row = [self.cursus.competences[competence_id].titre] + for annee in ("BUT1", "BUT2", "BUT3"): + validation = self.cursus.validation_par_competence_et_annee.get( + competence_id, {} + ).get(annee) + has_niveau = self.cursus.competence_annee_has_niveau( + competence_id, annee + ) + txt = "" + if validation: + txt = validation.code + elif has_niveau: + txt = "-" + row.append(txt) + rows.append(row) + + rows_styled = [ + [Paragraph(SU(str(cell)), self.style_niveaux_top) for cell in rows[0]] + ] + [ + [Paragraph(SU(str(row[0])), self.style_niveaux_titre)] + + [ + Paragraph(SU(str(cell)), self.style_niveaux_code) if cell else "" + for cell in row[1:] + ] + for row in rows[1:] + ] + + table = Table( + rows_styled, + colWidths=[ + self.width_col_niveaux_titre, + self.width_col_niveaux_code, + self.width_col_niveaux_code, + self.width_col_niveaux_code, + ], + style=[ + ("ALIGN", (0, 0), (-1, -1), "CENTER"), + ("VALIGN", (0, 0), (-1, -1), "MIDDLE"), + ("LEFTPADDING", (0, 0), (-1, -1), 2), + ("TOPPADDING", (0, 0), (-1, -1), 4), + ("RIGHTPADDING", (0, 0), (-1, -1), 2), + ("BOTTOMPADDING", (0, 0), (-1, -1), 4), + # sert de séparateur entre les lignes: + ("LINEABOVE", (0, 1), (-1, -1), 3, white), + ], + ) + table.hAlign = "LEFT" + return table + + def boite_decisions_jury(self): + """La boite en bas à droite avec jury""" + txt = f"""ECTS acquis : {self.ects_total}
""" + if self.bul["semestre"]["decision_annee"]: + txt += f""" + Jury tenu le { + datetime.datetime.fromisoformat(self.bul["semestre"]["decision_annee"]["date"]).strftime("%d/%m/%Y à %H:%M") + }, année BUT {self.bul["semestre"]["decision_annee"]["code"]}. +
+ """ + if self.bul["semestre"]["autorisation_inscription"]: + txt += ( + "Autorisé à s'inscrire en " + + ", ".join( + [ + f"S{aut['semestre_id']}" + for aut in self.bul["semestre"]["autorisation_inscription"] + ] + ) + + "." + ) + + return Paragraph(txt) diff --git a/app/scodoc/gen_tables.py b/app/scodoc/gen_tables.py index 2b5e867a9..0920b81f5 100644 --- a/app/scodoc/gen_tables.py +++ b/app/scodoc/gen_tables.py @@ -538,8 +538,8 @@ class GenTable: ] ) - def pdf(self): - "PDF representation: returns a ReportLab's platypus Table instance" + def pdf(self) -> list: + "PDF representation: returns a list of ReportLab's platypus objects" r = [] try: sco_pdf.PDFLOCK.acquire() @@ -548,12 +548,12 @@ class GenTable: sco_pdf.PDFLOCK.release() return r - def _pdf(self): + def _pdf(self) -> list: """PDF representation: returns a list of ReportLab's platypus objects (notably a Table instance) """ + LINEWIDTH = 0.5 if not self.pdf_table_style: - LINEWIDTH = 0.5 self.pdf_table_style = [ ("FONTNAME", (0, 0), (-1, 0), self.preferences["SCOLAR_FONT"]), ("LINEBELOW", (0, 0), (-1, 0), LINEWIDTH, Color(0, 0, 0)), @@ -570,7 +570,6 @@ class GenTable: CellStyle.fontSize = self.preferences["SCOLAR_FONT_SIZE"] CellStyle.fontName = self.preferences["SCOLAR_FONT"] CellStyle.leading = 1.0 * self.preferences["SCOLAR_FONT_SIZE"] # vertical space - LINEWIDTH = 0.5 # # titles = ["%s" % x for x in self.get_titles_list()] pdf_style_list = [] diff --git a/app/scodoc/sco_bulletins.py b/app/scodoc/sco_bulletins.py index ba27c9b96..a1e67d338 100644 --- a/app/scodoc/sco_bulletins.py +++ b/app/scodoc/sco_bulletins.py @@ -88,7 +88,6 @@ def get_formsemestre_bulletin_etud_json( return json_response( data_=bulletins_sem.bulletin_etud( etud, - formsemestre, force_publishing=force_publishing, version=version, ) diff --git a/app/scodoc/sco_bulletins_pdf.py b/app/scodoc/sco_bulletins_pdf.py index 314aff5d3..4413741d4 100644 --- a/app/scodoc/sco_bulletins_pdf.py +++ b/app/scodoc/sco_bulletins_pdf.py @@ -136,7 +136,7 @@ class WrapDict(object): try: value = self.dict[key] except KeyError: - return f"XXX {key} invalide XXX" + raise if value is None: return self.none_value return value @@ -168,6 +168,7 @@ def process_field(field, cdict, style, suppress_empty_pars=False, fmt="pdf"): scu.flash_once( f"Attention: format PDF invalide (champs {field}, clef {missing_key})" ) + raise except: # pylint: disable=bare-except log( f"""process_field: invalid format. field={field!r} diff --git a/app/scodoc/sco_pdf.py b/app/scodoc/sco_pdf.py index 24cd548b1..72e1f8df2 100755 --- a/app/scodoc/sco_pdf.py +++ b/app/scodoc/sco_pdf.py @@ -163,6 +163,7 @@ def make_paras(txt: str, style, suppress_empty=False) -> list[Paragraph]: style, ) ] + # si on voulait interrompre la génération raise ScoPDFFormatError("Erreur: format invalide") except ValueError: # probleme font ? essaye sans style # récupère font en cause ? m = re.match(r".*family/bold/italic for (.*)", exc.args[0], re.DOTALL) diff --git a/app/scodoc/sco_recapcomplet.py b/app/scodoc/sco_recapcomplet.py index 791fdb0f5..5560f2137 100644 --- a/app/scodoc/sco_recapcomplet.py +++ b/app/scodoc/sco_recapcomplet.py @@ -416,7 +416,7 @@ def gen_formsemestre_recapcomplet_json( if is_apc: etud = Identite.get_etud(etudid) bulletins_sem = bulletin_but.BulletinBUT(formsemestre) - bul = bulletins_sem.bulletin_etud(etud, formsemestre) + bul = bulletins_sem.bulletin_etud(etud) else: bul = sco_bulletins_json.formsemestre_bulletinetud_published_dict( formsemestre_id, diff --git a/app/templates/but/bulletin_court_page.j2 b/app/templates/but/bulletin_court_page.j2 index 593ec54f4..66da8bc39 100644 --- a/app/templates/but/bulletin_court_page.j2 +++ b/app/templates/but/bulletin_court_page.j2 @@ -12,11 +12,11 @@ - Unités d'enseignement + Unités d'enseignement {{title}} - {% for ue in bul.ues %} + {% for ue in ues_acronyms %} {{ue}} {% endfor %} @@ -26,7 +26,7 @@ {{mod}} {{bul[mod_type][mod].titre}} - {% for ue in bul.ues %} + {% for ue in ues_acronyms %} {{ bul.ues[ue][mod_type][mod].moyenne if mod in bul.ues[ue][mod_type] else "" @@ -75,7 +75,7 @@ - {% for ue in bul.ues %} + {% for ue in ues_acronyms %} {{ue}} {% endfor %} @@ -83,43 +83,45 @@ Moyenne - {% for ue in bul.ues %} + {% for ue in ues_acronyms %} {{bul.ues[ue].moyenne.value}} {% endfor %} Bonus - {% for ue in bul.ues %} + {% for ue in ues_acronyms %} {{bul.ues[ue].bonus if bul.ues[ue].bonus != "00.00" else ""}} {% endfor %} Malus - {% for ue in bul.ues %} + {% for ue in ues_acronyms %} {{bul.ues[ue].malus if bul.ues[ue].malus != "00.00" else ""}} {% endfor %} Rang - {% for ue in bul.ues %} + {% for ue in ues_acronyms %} {{bul.ues[ue].moyenne.rang}} {% endfor %} Effectif - {% for ue in bul.ues %} + {% for ue in ues_acronyms %} {{bul.ues[ue].moyenne.total}} {% endfor %} ECTS - {% for ue in bul.ues %} - {{bul.ues[ue].moyenne.ects}} + {% for ue in ues_acronyms %} + {{ + "%g"|format(decision_ues[ue].ects) if ue in decision_ues else "" + }} {% endfor %} Jury - {% for ue in bul.ues %} + {% for ue in ues_acronyms %} {{ decision_ues[ue].code if ue in decision_ues else "" }}