diff --git a/app/__init__.py b/app/__init__.py
index f1fba060..cf1495be 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 74d167eb..4cf2e88c 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 0396b1bc..4ba1aee9 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 173630c1..3f0a1fb6 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 2b5e867a..0920b81f 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 = ["
- Unités d'enseignement
+ Unités d'enseignement
@@ -26,7 +26,7 @@
{{title}}
- {% for ue in bul.ues %}
+ {% for ue in ues_acronyms %}
{{ue}}
{% endfor %}
{{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 @@
@@ -83,43 +83,45 @@
- {% for ue in bul.ues %}
+ {% for ue in ues_acronyms %}
{{ue}}
{% endfor %}