forked from ScoDoc/ScoDoc
Update opolka/ScoDoc from ScoDoc/ScoDoc #2
@ -34,9 +34,9 @@ from flask_sqlalchemy import SQLAlchemy
|
|||||||
|
|
||||||
from jinja2 import select_autoescape
|
from jinja2 import select_autoescape
|
||||||
import sqlalchemy as sa
|
import sqlalchemy as sa
|
||||||
|
import werkzeug.debug
|
||||||
|
|
||||||
from flask_cas import CAS
|
from flask_cas import CAS
|
||||||
import werkzeug.debug
|
|
||||||
|
|
||||||
from app.scodoc.sco_exceptions import (
|
from app.scodoc.sco_exceptions import (
|
||||||
AccessDenied,
|
AccessDenied,
|
||||||
@ -44,6 +44,7 @@ from app.scodoc.sco_exceptions import (
|
|||||||
ScoException,
|
ScoException,
|
||||||
ScoGenError,
|
ScoGenError,
|
||||||
ScoInvalidCSRF,
|
ScoInvalidCSRF,
|
||||||
|
ScoPDFFormatError,
|
||||||
ScoValueError,
|
ScoValueError,
|
||||||
APIInvalidParams,
|
APIInvalidParams,
|
||||||
)
|
)
|
||||||
@ -74,6 +75,7 @@ cache = Cache(
|
|||||||
|
|
||||||
|
|
||||||
def handle_sco_value_error(exc):
|
def handle_sco_value_error(exc):
|
||||||
|
"page d'erreur avec message"
|
||||||
return render_template("sco_value_error.j2", exc=exc), 404
|
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
|
return render_template("error_csrf.j2", exc=exc), 404
|
||||||
|
|
||||||
|
|
||||||
|
def handle_pdf_format_error(exc):
|
||||||
|
return "ay ay ay"
|
||||||
|
|
||||||
|
|
||||||
def internal_server_error(exc):
|
def internal_server_error(exc):
|
||||||
"""Bugs scodoc, erreurs 500"""
|
"""Bugs scodoc, erreurs 500"""
|
||||||
# note that we set the 500 status explicitly
|
# 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(ScoValueError, handle_sco_value_error)
|
||||||
app.register_error_handler(ScoBugCatcher, handle_sco_bug)
|
app.register_error_handler(ScoBugCatcher, handle_sco_bug)
|
||||||
app.register_error_handler(ScoInvalidCSRF, handle_invalid_csrf)
|
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(AccessDenied, handle_access_denied)
|
||||||
app.register_error_handler(500, internal_server_error)
|
app.register_error_handler(500, internal_server_error)
|
||||||
app.register_error_handler(503, postgresql_server_error)
|
app.register_error_handler(503, postgresql_server_error)
|
||||||
|
@ -333,7 +333,6 @@ class BulletinBUT:
|
|||||||
def bulletin_etud(
|
def bulletin_etud(
|
||||||
self,
|
self,
|
||||||
etud: Identite,
|
etud: Identite,
|
||||||
formsemestre: FormSemestre,
|
|
||||||
force_publishing=False,
|
force_publishing=False,
|
||||||
version="long",
|
version="long",
|
||||||
) -> dict:
|
) -> dict:
|
||||||
@ -346,6 +345,7 @@ class BulletinBUT:
|
|||||||
(bulletins non publiés).
|
(bulletins non publiés).
|
||||||
"""
|
"""
|
||||||
res = self.res
|
res = self.res
|
||||||
|
formsemestre = res.formsemestre
|
||||||
etat_inscription = etud.inscription_etat(formsemestre.id)
|
etat_inscription = etud.inscription_etat(formsemestre.id)
|
||||||
nb_inscrits = self.res.get_inscriptions_counts()[scu.INSCRIT]
|
nb_inscrits = self.res.get_inscriptions_counts()[scu.INSCRIT]
|
||||||
published = (not formsemestre.bul_hide_xml) or force_publishing
|
published = (not formsemestre.bul_hide_xml) or force_publishing
|
||||||
@ -489,9 +489,7 @@ class BulletinBUT:
|
|||||||
(pas utilisé pour json/html)
|
(pas utilisé pour json/html)
|
||||||
Résultat compatible avec celui de sco_bulletins.formsemestre_bulletinetud_dict
|
Résultat compatible avec celui de sco_bulletins.formsemestre_bulletinetud_dict
|
||||||
"""
|
"""
|
||||||
d = self.bulletin_etud(
|
d = self.bulletin_etud(etud, version=version, force_publishing=True)
|
||||||
etud, self.res.formsemestre, version=version, force_publishing=True
|
|
||||||
)
|
|
||||||
d["etudid"] = etud.id
|
d["etudid"] = etud.id
|
||||||
d["etud"] = d["etudiant"]
|
d["etud"] = d["etudiant"]
|
||||||
d["etud"]["nomprenom"] = etud.nomprenom
|
d["etud"]["nomprenom"] = etud.nomprenom
|
||||||
|
@ -25,8 +25,8 @@ Ces données sont des objets passés au template.
|
|||||||
import datetime
|
import datetime
|
||||||
import time
|
import time
|
||||||
|
|
||||||
from flask import render_template, url_for
|
from flask import render_template
|
||||||
from flask import g, request
|
from flask import g
|
||||||
|
|
||||||
from app.but.bulletin_but import BulletinBUT
|
from app.but.bulletin_but import BulletinBUT
|
||||||
from app.but import bulletin_but_court_pdf, cursus_but, validations_view
|
from app.but import bulletin_but_court_pdf, cursus_but, validations_view
|
||||||
@ -35,6 +35,7 @@ from app.decorators import (
|
|||||||
permission_required,
|
permission_required,
|
||||||
)
|
)
|
||||||
from app.models import FormSemestre, FormSemestreInscription, Identite
|
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_exceptions import ScoNoReferentielCompetences
|
||||||
from app.scodoc.sco_logos import find_logo
|
from app.scodoc.sco_logos import find_logo
|
||||||
from app.scodoc.sco_permissions import Permission
|
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()
|
.first_or_404()
|
||||||
)
|
)
|
||||||
bulletins_sem = BulletinBUT(formsemestre)
|
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"]}
|
decision_ues = {x["acronyme"]: x for x in bul["semestre"]["decision_ue"]}
|
||||||
cursus = cursus_but.EtudCursusBUT(etud, formsemestre.formation)
|
cursus = cursus_but.EtudCursusBUT(etud, formsemestre.formation)
|
||||||
refcomp = formsemestre.formation.referentiel_competence
|
refcomp = formsemestre.formation.referentiel_competence
|
||||||
@ -82,8 +86,10 @@ def bulletin_but(formsemestre_id: int, etudid: int = None, fmt="html"):
|
|||||||
"logo": logo,
|
"logo": logo,
|
||||||
"title": f"Bul. {etud.nom_disp()} BUT (court)",
|
"title": f"Bul. {etud.nom_disp()} BUT (court)",
|
||||||
"ue_validation_by_niveau": ue_validation_by_niveau,
|
"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":
|
if fmt == "pdf":
|
||||||
filename = scu.bul_filename(formsemestre, etud, prefix="bul-but")
|
filename = scu.bul_filename(formsemestre, etud, prefix="bul-but")
|
||||||
bul_pdf = bulletin_but_court_pdf.make_bulletin_but_court_pdf(**args)
|
bul_pdf = bulletin_but_court_pdf.make_bulletin_but_court_pdf(**args)
|
||||||
|
@ -10,19 +10,23 @@ On génère du PDF avec reportLab en utilisant les classes
|
|||||||
ScoDoc BulletinGenerator et GenTable.
|
ScoDoc BulletinGenerator et GenTable.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
import datetime
|
||||||
from flask_login import current_user
|
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.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.but import cursus_but
|
||||||
from app.models import FormSemestre, Identite, ScolarFormSemestreValidation
|
from app.models import FormSemestre, Identite, ScolarFormSemestreValidation
|
||||||
|
|
||||||
from app.scodoc.sco_bulletins_standard import BulletinGeneratorStandard
|
from app.scodoc.sco_bulletins_standard import BulletinGeneratorStandard
|
||||||
|
from app.scodoc import sco_bulletins
|
||||||
from app.scodoc.sco_logos import Logo
|
from app.scodoc.sco_logos import Logo
|
||||||
from app.scodoc import gen_tables, sco_pdf, sco_preferences
|
from app.scodoc import sco_pdf, sco_preferences
|
||||||
from app.scodoc.sco_pdf import PDFLOCK
|
from app.scodoc.sco_pdf import PDFLOCK, SU
|
||||||
|
|
||||||
|
|
||||||
def make_bulletin_but_court_pdf(
|
def make_bulletin_but_court_pdf(
|
||||||
@ -35,6 +39,7 @@ def make_bulletin_but_court_pdf(
|
|||||||
logo: Logo = None,
|
logo: Logo = None,
|
||||||
title: str = "",
|
title: str = "",
|
||||||
ue_validation_by_niveau: dict[tuple[int, str], ScolarFormSemestreValidation] = None,
|
ue_validation_by_niveau: dict[tuple[int, str], ScolarFormSemestreValidation] = None,
|
||||||
|
ues_acronyms: list[str] = None,
|
||||||
) -> bytes:
|
) -> bytes:
|
||||||
# A priori ce verrou n'est plus nécessaire avec Flask (multi-process)
|
# A priori ce verrou n'est plus nécessaire avec Flask (multi-process)
|
||||||
# mais...
|
# mais...
|
||||||
@ -58,6 +63,7 @@ class BulletinGeneratorBUTCourt(BulletinGeneratorStandard):
|
|||||||
scale_table_in_page = True # pas de mise à l'échelle pleine page auto
|
scale_table_in_page = True # pas de mise à l'échelle pleine page auto
|
||||||
multi_pages = False # une page par bulletin
|
multi_pages = False # une page par bulletin
|
||||||
small_fontsize = "8"
|
small_fontsize = "8"
|
||||||
|
color_blue_bg = Color(0, 153 / 255, 204 / 255)
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
@ -72,16 +78,9 @@ class BulletinGeneratorBUTCourt(BulletinGeneratorStandard):
|
|||||||
ue_validation_by_niveau: dict[
|
ue_validation_by_niveau: dict[
|
||||||
tuple[int, str], ScolarFormSemestreValidation
|
tuple[int, str], ScolarFormSemestreValidation
|
||||||
] = None,
|
] = None,
|
||||||
|
ues_acronyms: list[str] = None,
|
||||||
):
|
):
|
||||||
# données pour anciens codes bulletins... à moderniser
|
super().__init__(bul, authuser=current_user)
|
||||||
infos = {
|
|
||||||
"etud": etud.to_dict_bul(),
|
|
||||||
"filigranne": None,
|
|
||||||
"formsemestre_id": formsemestre.id,
|
|
||||||
"nbabs": 0,
|
|
||||||
"nbabsjust": 0,
|
|
||||||
}
|
|
||||||
super().__init__(infos, authuser=current_user)
|
|
||||||
self.bul = bul
|
self.bul = bul
|
||||||
self.cursus = cursus
|
self.cursus = cursus
|
||||||
self.decision_ues = decision_ues
|
self.decision_ues = decision_ues
|
||||||
@ -91,32 +90,334 @@ class BulletinGeneratorBUTCourt(BulletinGeneratorStandard):
|
|||||||
self.logo = logo
|
self.logo = logo
|
||||||
self.title = title
|
self.title = title
|
||||||
self.ue_validation_by_niveau = ue_validation_by_niveau
|
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
|
"""Génère la table centrale du bulletin de notes
|
||||||
Renvoie: une liste d'objets PLATYPUS (eg instance de Table).
|
Renvoie: une liste d'objets PLATYPUS (eg instance de Table).
|
||||||
L'argument fmt est ici ignoré (toujours en PDF)
|
L'argument fmt est ici ignoré (toujours en PDF)
|
||||||
"""
|
"""
|
||||||
ue_table = self.build_ue_table()
|
style_table_2cols = [
|
||||||
|
("ALIGN", (0, -1), (0, -1), "LEFT"),
|
||||||
return ue_table.gen(format="pdf")
|
("ALIGN", (-1, -1), (-1, -1), "RIGHT"),
|
||||||
|
("VALIGN", (0, 0), (-1, 1), "TOP"),
|
||||||
def build_ue_table(self) -> gen_tables.GenTable:
|
("LEFTPADDING", (0, 0), (-1, -1), 0),
|
||||||
"""Table avec les résultats d'UE du semestre courant"""
|
("TOPPADDING", (0, 0), (-1, -1), 0),
|
||||||
columns_ids = ("titre", "UE1", "UE2")
|
("RIGHTPADDING", (0, 0), (-1, -1), 0),
|
||||||
rows = [
|
("BOTTOMPADDING", (0, 0), (-1, -1), 0),
|
||||||
{"titre": "ligne 1", "UE1": "12.", "UE2": "13"},
|
|
||||||
{"titre": "ligne 2", "UE1": "14.", "UE2": "15"},
|
|
||||||
]
|
]
|
||||||
pdf_style = [
|
# Ligne avec boite assiduité et table UEs
|
||||||
("VALIGN", (0, 0), (-1, -1), "TOP"),
|
table_abs_ues = Table(
|
||||||
("BOX", (0, 0), (-1, -1), 0.4, blue), # ajoute cadre extérieur bleu
|
[[self.box_assiduite(), self.table_ues()]],
|
||||||
]
|
colWidths=(3 * cm, self.width_page_avail - 3 * cm),
|
||||||
col_widths = {"titre": 3 * cm, "UE1": 1 * cm, "UE2": 1 * cm}
|
style=style_table_2cols,
|
||||||
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),
|
|
||||||
)
|
)
|
||||||
|
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}<br/>"""
|
||||||
|
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"]}.
|
||||||
|
<br/>
|
||||||
|
"""
|
||||||
|
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)
|
||||||
|
@ -538,8 +538,8 @@ class GenTable:
|
|||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
def pdf(self):
|
def pdf(self) -> list:
|
||||||
"PDF representation: returns a ReportLab's platypus Table instance"
|
"PDF representation: returns a list of ReportLab's platypus objects"
|
||||||
r = []
|
r = []
|
||||||
try:
|
try:
|
||||||
sco_pdf.PDFLOCK.acquire()
|
sco_pdf.PDFLOCK.acquire()
|
||||||
@ -548,12 +548,12 @@ class GenTable:
|
|||||||
sco_pdf.PDFLOCK.release()
|
sco_pdf.PDFLOCK.release()
|
||||||
return r
|
return r
|
||||||
|
|
||||||
def _pdf(self):
|
def _pdf(self) -> list:
|
||||||
"""PDF representation: returns a list of ReportLab's platypus objects
|
"""PDF representation: returns a list of ReportLab's platypus objects
|
||||||
(notably a Table instance)
|
(notably a Table instance)
|
||||||
"""
|
"""
|
||||||
if not self.pdf_table_style:
|
|
||||||
LINEWIDTH = 0.5
|
LINEWIDTH = 0.5
|
||||||
|
if not self.pdf_table_style:
|
||||||
self.pdf_table_style = [
|
self.pdf_table_style = [
|
||||||
("FONTNAME", (0, 0), (-1, 0), self.preferences["SCOLAR_FONT"]),
|
("FONTNAME", (0, 0), (-1, 0), self.preferences["SCOLAR_FONT"]),
|
||||||
("LINEBELOW", (0, 0), (-1, 0), LINEWIDTH, Color(0, 0, 0)),
|
("LINEBELOW", (0, 0), (-1, 0), LINEWIDTH, Color(0, 0, 0)),
|
||||||
@ -570,7 +570,6 @@ class GenTable:
|
|||||||
CellStyle.fontSize = self.preferences["SCOLAR_FONT_SIZE"]
|
CellStyle.fontSize = self.preferences["SCOLAR_FONT_SIZE"]
|
||||||
CellStyle.fontName = self.preferences["SCOLAR_FONT"]
|
CellStyle.fontName = self.preferences["SCOLAR_FONT"]
|
||||||
CellStyle.leading = 1.0 * self.preferences["SCOLAR_FONT_SIZE"] # vertical space
|
CellStyle.leading = 1.0 * self.preferences["SCOLAR_FONT_SIZE"] # vertical space
|
||||||
LINEWIDTH = 0.5
|
|
||||||
#
|
#
|
||||||
# titles = ["<para><b>%s</b></para>" % x for x in self.get_titles_list()]
|
# titles = ["<para><b>%s</b></para>" % x for x in self.get_titles_list()]
|
||||||
pdf_style_list = []
|
pdf_style_list = []
|
||||||
|
@ -88,7 +88,6 @@ def get_formsemestre_bulletin_etud_json(
|
|||||||
return json_response(
|
return json_response(
|
||||||
data_=bulletins_sem.bulletin_etud(
|
data_=bulletins_sem.bulletin_etud(
|
||||||
etud,
|
etud,
|
||||||
formsemestre,
|
|
||||||
force_publishing=force_publishing,
|
force_publishing=force_publishing,
|
||||||
version=version,
|
version=version,
|
||||||
)
|
)
|
||||||
|
@ -136,7 +136,7 @@ class WrapDict(object):
|
|||||||
try:
|
try:
|
||||||
value = self.dict[key]
|
value = self.dict[key]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
return f"XXX {key} invalide XXX"
|
raise
|
||||||
if value is None:
|
if value is None:
|
||||||
return self.none_value
|
return self.none_value
|
||||||
return value
|
return value
|
||||||
@ -168,6 +168,7 @@ def process_field(field, cdict, style, suppress_empty_pars=False, fmt="pdf"):
|
|||||||
scu.flash_once(
|
scu.flash_once(
|
||||||
f"Attention: format PDF invalide (champs {field}, clef {missing_key})"
|
f"Attention: format PDF invalide (champs {field}, clef {missing_key})"
|
||||||
)
|
)
|
||||||
|
raise
|
||||||
except: # pylint: disable=bare-except
|
except: # pylint: disable=bare-except
|
||||||
log(
|
log(
|
||||||
f"""process_field: invalid format. field={field!r}
|
f"""process_field: invalid format. field={field!r}
|
||||||
|
@ -163,6 +163,7 @@ def make_paras(txt: str, style, suppress_empty=False) -> list[Paragraph]:
|
|||||||
style,
|
style,
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
|
# si on voulait interrompre la génération raise ScoPDFFormatError("Erreur: format invalide")
|
||||||
except ValueError: # probleme font ? essaye sans style
|
except ValueError: # probleme font ? essaye sans style
|
||||||
# récupère font en cause ?
|
# récupère font en cause ?
|
||||||
m = re.match(r".*family/bold/italic for (.*)", exc.args[0], re.DOTALL)
|
m = re.match(r".*family/bold/italic for (.*)", exc.args[0], re.DOTALL)
|
||||||
|
@ -416,7 +416,7 @@ def gen_formsemestre_recapcomplet_json(
|
|||||||
if is_apc:
|
if is_apc:
|
||||||
etud = Identite.get_etud(etudid)
|
etud = Identite.get_etud(etudid)
|
||||||
bulletins_sem = bulletin_but.BulletinBUT(formsemestre)
|
bulletins_sem = bulletin_but.BulletinBUT(formsemestre)
|
||||||
bul = bulletins_sem.bulletin_etud(etud, formsemestre)
|
bul = bulletins_sem.bulletin_etud(etud)
|
||||||
else:
|
else:
|
||||||
bul = sco_bulletins_json.formsemestre_bulletinetud_published_dict(
|
bul = sco_bulletins_json.formsemestre_bulletinetud_published_dict(
|
||||||
formsemestre_id,
|
formsemestre_id,
|
||||||
|
@ -12,11 +12,11 @@
|
|||||||
<thead>
|
<thead>
|
||||||
<tr class="titre_table">
|
<tr class="titre_table">
|
||||||
<th colspan="2"></th>
|
<th colspan="2"></th>
|
||||||
<th colspan="{{ bul.ues|length }}">Unités d'enseignement</th>
|
<th colspan="{{ ues_acronyms|length }}">Unités d'enseignement</th>
|
||||||
</tr>
|
</tr>
|
||||||
<tr class="titres_ues">
|
<tr class="titres_ues">
|
||||||
<td colspan="2">{{title}}</td>
|
<td colspan="2">{{title}}</td>
|
||||||
{% for ue in bul.ues %}
|
{% for ue in ues_acronyms %}
|
||||||
<td class="col_ue">{{ue}}</td>
|
<td class="col_ue">{{ue}}</td>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tr>
|
</tr>
|
||||||
@ -26,7 +26,7 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<td>{{mod}}</td>
|
<td>{{mod}}</td>
|
||||||
<td>{{bul[mod_type][mod].titre}}</td>
|
<td>{{bul[mod_type][mod].titre}}</td>
|
||||||
{% for ue in bul.ues %}
|
{% for ue in ues_acronyms %}
|
||||||
<td>{{
|
<td>{{
|
||||||
bul.ues[ue][mod_type][mod].moyenne
|
bul.ues[ue][mod_type][mod].moyenne
|
||||||
if mod in bul.ues[ue][mod_type] else ""
|
if mod in bul.ues[ue][mod_type] else ""
|
||||||
@ -75,7 +75,7 @@
|
|||||||
</tr>
|
</tr>
|
||||||
<tr class="titres_ues">
|
<tr class="titres_ues">
|
||||||
<td></td>
|
<td></td>
|
||||||
{% for ue in bul.ues %}
|
{% for ue in ues_acronyms %}
|
||||||
<td class="col_ue">{{ue}}</td>
|
<td class="col_ue">{{ue}}</td>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tr>
|
</tr>
|
||||||
@ -83,43 +83,45 @@
|
|||||||
<tbody>
|
<tbody>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Moyenne</td>
|
<td>Moyenne</td>
|
||||||
{% for ue in bul.ues %}
|
{% for ue in ues_acronyms %}
|
||||||
<td class="col_ue">{{bul.ues[ue].moyenne.value}}</td>
|
<td class="col_ue">{{bul.ues[ue].moyenne.value}}</td>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Bonus</td>
|
<td>Bonus</td>
|
||||||
{% for ue in bul.ues %}
|
{% for ue in ues_acronyms %}
|
||||||
<td class="col_ue">{{bul.ues[ue].bonus if bul.ues[ue].bonus != "00.00" else ""}}</td>
|
<td class="col_ue">{{bul.ues[ue].bonus if bul.ues[ue].bonus != "00.00" else ""}}</td>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Malus</td>
|
<td>Malus</td>
|
||||||
{% for ue in bul.ues %}
|
{% for ue in ues_acronyms %}
|
||||||
<td class="col_ue">{{bul.ues[ue].malus if bul.ues[ue].malus != "00.00" else ""}}</td>
|
<td class="col_ue">{{bul.ues[ue].malus if bul.ues[ue].malus != "00.00" else ""}}</td>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Rang</td>
|
<td>Rang</td>
|
||||||
{% for ue in bul.ues %}
|
{% for ue in ues_acronyms %}
|
||||||
<td class="col_ue">{{bul.ues[ue].moyenne.rang}}</td>
|
<td class="col_ue">{{bul.ues[ue].moyenne.rang}}</td>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Effectif</td>
|
<td>Effectif</td>
|
||||||
{% for ue in bul.ues %}
|
{% for ue in ues_acronyms %}
|
||||||
<td class="col_ue">{{bul.ues[ue].moyenne.total}}</td>
|
<td class="col_ue">{{bul.ues[ue].moyenne.total}}</td>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>ECTS</td>
|
<td>ECTS</td>
|
||||||
{% for ue in bul.ues %}
|
{% for ue in ues_acronyms %}
|
||||||
<td class="col_ue">{{bul.ues[ue].moyenne.ects}}</td>
|
<td class="col_ue">{{
|
||||||
|
"%g"|format(decision_ues[ue].ects) if ue in decision_ues else ""
|
||||||
|
}}</td>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tr>
|
</tr>
|
||||||
<tr class="jury">
|
<tr class="jury">
|
||||||
<td>Jury</td>
|
<td>Jury</td>
|
||||||
{% for ue in bul.ues %}
|
{% for ue in ues_acronyms %}
|
||||||
<td class="col_ue">{{
|
<td class="col_ue">{{
|
||||||
decision_ues[ue].code if ue in decision_ues else ""
|
decision_ues[ue].code if ue in decision_ues else ""
|
||||||
}}</td>
|
}}</td>
|
||||||
|
Loading…
Reference in New Issue
Block a user