WIP: bulletins BUT pdf

This commit is contained in:
Emmanuel Viennet 2022-02-21 19:25:38 +01:00
commit 6b8b0f9c24
15 changed files with 332 additions and 92 deletions

View File

@ -201,7 +201,7 @@ def create_app(config_class=DevConfig):
app.register_blueprint(auth_bp, url_prefix="/auth") app.register_blueprint(auth_bp, url_prefix="/auth")
from app.entreprises import bp as entreprises_bp from app.entreprises import bp as entreprises_bp
app.register_blueprint(entreprises_bp, url_prefix="/ScoDoc/entreprises") app.register_blueprint(entreprises_bp, url_prefix="/ScoDoc/entreprises")
from app.views import scodoc_bp from app.views import scodoc_bp
@ -295,10 +295,12 @@ def create_app(config_class=DevConfig):
from app.scodoc.sco_bulletins_legacy import BulletinGeneratorLegacy from app.scodoc.sco_bulletins_legacy import BulletinGeneratorLegacy
from app.scodoc.sco_bulletins_standard import BulletinGeneratorStandard from app.scodoc.sco_bulletins_standard import BulletinGeneratorStandard
from app.but.bulletin_but_pdf import BulletinGeneratorStandardBUT
from app.scodoc.sco_bulletins_ucac import BulletinGeneratorUCAC from app.scodoc.sco_bulletins_ucac import BulletinGeneratorUCAC
# l'ordre est important, le premeir sera le "défaut" pour les nouveaux départements. # l'ordre est important, le premier sera le "défaut" pour les nouveaux départements.
sco_bulletins_generator.register_bulletin_class(BulletinGeneratorStandard) sco_bulletins_generator.register_bulletin_class(BulletinGeneratorStandard)
sco_bulletins_generator.register_bulletin_class(BulletinGeneratorStandardBUT)
sco_bulletins_generator.register_bulletin_class(BulletinGeneratorLegacy) sco_bulletins_generator.register_bulletin_class(BulletinGeneratorLegacy)
sco_bulletins_generator.register_bulletin_class(BulletinGeneratorUCAC) sco_bulletins_generator.register_bulletin_class(BulletinGeneratorUCAC)
if app.testing or app.debug: if app.testing or app.debug:

View File

@ -11,8 +11,8 @@ import datetime
from flask import url_for, g from flask import url_for, g
from app.comp.res_but import ResultatsSemestreBUT from app.comp.res_but import ResultatsSemestreBUT
from app.models import FormSemestre, Identite from app.models import FormSemestre, Identite, formsemestre
from app.scodoc import sco_utils as scu from app.scodoc import sco_bulletins, sco_utils as scu
from app.scodoc import sco_bulletins_json from app.scodoc import sco_bulletins_json
from app.scodoc import sco_bulletins_pdf from app.scodoc import sco_bulletins_pdf
from app.scodoc import sco_preferences from app.scodoc import sco_preferences
@ -217,7 +217,7 @@ class BulletinBUT:
return f"Bonus de {fmt_note(bonus_vect.iloc[0])}" return f"Bonus de {fmt_note(bonus_vect.iloc[0])}"
def bulletin_etud( def bulletin_etud(
self, etud: Identite, formsemestre, force_publishing=False self, etud: Identite, formsemestre: FormSemestre, force_publishing=False
) -> dict: ) -> dict:
"""Le bulletin de l'étudiant dans ce semestre: dict pour la version JSON / HTML. """Le bulletin de l'étudiant dans ce semestre: dict pour la version JSON / HTML.
- Si force_publishing, rempli le bulletin même si bul_hide_xml est vrai - Si force_publishing, rempli le bulletin même si bul_hide_xml est vrai
@ -317,11 +317,29 @@ class BulletinBUT:
return d return d
def bulletin_etud_complet(self, etud) -> dict: def bulletin_etud_complet(self, etud: Identite) -> dict:
"""Bulletin dict complet avec toutes les infos pour les bulletins pdf""" """Bulletin dict complet avec toutes les infos pour les bulletins pdf"""
d = self.bulletin_etud(force_publishing=True) d = self.bulletin_etud(etud, self.res.formsemestre, force_publishing=True)
d["etudid"] = etud.id
d["etud"] = d["etudiant"]
d["etud"]["nomprenom"] = etud.nomprenom
d.update(self.res.sem)
d["filigranne"] = sco_bulletins_pdf.get_filigranne( d["filigranne"] = sco_bulletins_pdf.get_filigranne(
self.res.get_etud_etat(etud.id), self.prefs self.res.get_etud_etat(etud.id),
self.prefs,
decision_sem=d["semestre"].get("decision_sem"),
) )
# XXX TODO A COMPLETER # --- Absences
raise NotImplementedError() d["nbabs"], d["nbabsjust"] = self.res.formsemestre.get_abs_count(etud.id)
# --- Rangs
d[
"rang_nt"
] = f"{d['semestre']['rang']['value']} / {d['semestre']['rang']['total']}"
d["rang_txt"] = "Rang " + d["rang_nt"]
# --- Appréciations
d.update(
sco_bulletins.get_appreciations_list(self.res.formsemestre.id, etud.id)
)
# XXX TODO A COMPLETER ?
return d

116
app/but/bulletin_but_pdf.py Normal file
View File

@ -0,0 +1,116 @@
##############################################################################
# ScoDoc
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
# See LICENSE
##############################################################################
"""Génération bulletin BUT au format PDF standard
"""
import datetime
from app.scodoc.sco_pdf import blue, cm, mm
from flask import url_for, g
from app.models.formsemestre import FormSemestre
from app.scodoc import gen_tables
from app.scodoc import sco_utils as scu
from app.scodoc import sco_bulletins_json
from app.scodoc import sco_preferences
from app.scodoc.sco_codes_parcours import UE_SPORT
from app.scodoc.sco_utils import fmt_note
from app.comp.res_but import ResultatsSemestreBUT
from app.scodoc.sco_bulletins_standard import BulletinGeneratorStandard
class BulletinGeneratorStandardBUT(BulletinGeneratorStandard):
"""Génération du bulletin de BUT au format PDF.
self.infos est le dict issu de BulletinBUT.bulletin_etud_complet()
"""
list_in_menu = False # spécialisation du BulletinGeneratorStandard, ne pas présenter à l'utilisateur
def bul_table(self, format="html"):
"""Génère la table centrale du bulletin de notes
Renvoie:
- en HTML: une chaine
- en PDF: une liste d'objets PLATYPUS (eg instance de Table).
"""
formsemestre_id = self.infos["formsemestre_id"]
(
synth_col_keys,
synth_P,
synth_pdf_style,
synth_col_widths,
) = self.but_table_synthese()
#
table_synthese = gen_tables.GenTable(
rows=synth_P,
columns_ids=synth_col_keys,
pdf_table_style=synth_pdf_style,
pdf_col_widths=[synth_col_widths[k] for k in synth_col_keys],
preferences=self.preferences,
html_class="notes_bulletin",
html_class_ignore_default=True,
html_with_td_classes=True,
)
# Ici on ajoutera table des ressources, tables des UE
# TODO
# XXX à modifier pour générer plusieurs tables:
return table_synthese.gen(format=format)
def but_table_synthese(self):
"""La table de synthèse; pour chaque UE, liste des ressources et SAÉs avec leurs notes
et leurs coefs.
Renvoie: colkeys, P, pdf_style, colWidths
- colkeys: nom des colonnes de la table (clés)
- P : table (liste de dicts de chaines de caracteres)
- pdf_style : commandes table Platypus
- largeurs de colonnes pour PDF
"""
col_widths = {
"titre": None,
"moyenne": 2 * cm,
"coef": 2 * cm,
}
P = [] # elems pour générer table avec gen_table (liste de dicts)
col_keys = ["titre", "moyenne"] # noms des colonnes à afficher
for ue_acronym, ue in self.infos["ues"].items():
# 1er ligne titre UE
moy_ue = ue.get("moyenne")
t = {
"titre": f"{ue_acronym} - {ue['titre']}",
"moyenne": moy_ue.get("value", "-") if moy_ue is not None else "-",
"_css_row_class": "note_bold",
"_pdf_row_markup": ["b"],
"_pdf_style": [],
}
P.append(t)
# 2eme ligne titre UE (bonus/malus/ects)
t = {
"titre": "",
"moyenne": f"""Bonus: {ue['bonus']} - Malus: {
ue["malus"]} - ECTS: {ue["ECTS"]["acquis"]} / {ue["ECTS"]["total"]}""",
"_css_row_class": "note_bold",
"_pdf_row_markup": ["b"],
"_pdf_style": [
(
"LINEBELOW",
(0, 0),
(-1, 0),
self.PDF_LINEWIDTH,
self.PDF_LINECOLOR,
)
],
}
P.append(t)
# Global pdf style commands:
pdf_style = [
("VALIGN", (0, 0), (-1, -1), "TOP"),
("BOX", (0, 0), (-1, -1), 0.4, blue), # ajoute cadre extérieur bleu:
]
return col_keys, P, pdf_style, col_widths

52
app/comp/moy_mat.py Normal file
View File

@ -0,0 +1,52 @@
##############################################################################
# ScoDoc
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
# See LICENSE
##############################################################################
"""Calcul des moyennes de matières
"""
# C'est un recalcul (optionnel) effectué _après_ le calcul standard.
import numpy as np
import pandas as pd
from app.comp import moy_ue
from app.models.formsemestre import FormSemestre
from app.scodoc.sco_codes_parcours import UE_SPORT
from app.scodoc.sco_utils import ModuleType
def compute_mat_moys_classic(
formsemestre: FormSemestre,
sem_matrix: np.array,
ues: list,
modimpl_inscr_df: pd.DataFrame,
modimpl_coefs: np.array,
) -> dict:
"""Calcul des moyennes par matières.
Result: dict, { matiere_id : Series, index etudid }
"""
modimpls_std = [
m
for m in formsemestre.modimpls_sorted
if (m.module.module_type == ModuleType.STANDARD)
and (m.module.ue.type != UE_SPORT)
]
matiere_ids = {m.module.matiere.id for m in modimpls_std}
matiere_moy = {} # { matiere_id : moy pd.Series, index etudid }
for matiere_id in matiere_ids:
modimpl_mask = np.array(
[m.module.matiere.id == matiere_id for m in formsemestre.modimpls_sorted]
)
etud_moy_gen, _, _ = moy_ue.compute_ue_moys_classic(
formsemestre,
sem_matrix=sem_matrix,
ues=ues,
modimpl_inscr_df=modimpl_inscr_df,
modimpl_coefs=modimpl_coefs,
modimpl_mask=modimpl_mask,
)
matiere_moy[matiere_id] = etud_moy_gen
return matiere_moy

View File

@ -27,7 +27,6 @@
"""Fonctions de calcul des moyennes d'UE (classiques ou BUT) """Fonctions de calcul des moyennes d'UE (classiques ou BUT)
""" """
from re import X
import numpy as np import numpy as np
import pandas as pd import pandas as pd

View File

@ -15,7 +15,7 @@ from flask import g, url_for
from app import db from app import db
from app import log from app import log
from app.comp import moy_mod, moy_ue, inscr_mod from app.comp import moy_mat, moy_mod, moy_ue, inscr_mod
from app.comp.res_common import NotesTableCompat from app.comp.res_common import NotesTableCompat
from app.comp.bonus_spo import BonusSport from app.comp.bonus_spo import BonusSport
from app.models import ScoDocSiteConfig from app.models import ScoDocSiteConfig
@ -24,6 +24,7 @@ from app.models.formsemestre import FormSemestre
from app.models.ues import UniteEns from app.models.ues import UniteEns
from app.scodoc.sco_codes_parcours import UE_SPORT from app.scodoc.sco_codes_parcours import UE_SPORT
from app.scodoc.sco_exceptions import ScoValueError from app.scodoc.sco_exceptions import ScoValueError
from app.scodoc import sco_preferences
from app.scodoc.sco_utils import ModuleType from app.scodoc.sco_utils import ModuleType
@ -133,6 +134,10 @@ class ResultatsSemestreClassic(NotesTableCompat):
# --- Classements: # --- Classements:
self.compute_rangs() self.compute_rangs()
# --- En option, moyennes par matières
if sco_preferences.get_preference("bul_show_matieres", self.formsemestre.id):
self.compute_moyennes_matieres()
def get_etud_mod_moy(self, moduleimpl_id: int, etudid: int) -> float: def get_etud_mod_moy(self, moduleimpl_id: int, etudid: int) -> float:
"""La moyenne de l'étudiant dans le moduleimpl """La moyenne de l'étudiant dans le moduleimpl
Result: valeur float (peut être NaN) ou chaîne "NI" (non inscrit ou DEM) Result: valeur float (peut être NaN) ou chaîne "NI" (non inscrit ou DEM)
@ -158,6 +163,16 @@ class ResultatsSemestreClassic(NotesTableCompat):
), ),
} }
def compute_moyennes_matieres(self):
"""Calcul les moyennes par matière. Doit être appelée au besoin, en fin de compute."""
self.moyennes_matieres = moy_mat.compute_mat_moys_classic(
self.formsemestre,
self.sem_matrix,
self.ues,
self.modimpl_inscr_df,
self.modimpl_coefs,
)
def compute_etud_ue_coef(self, etudid: int, ue: UniteEns) -> float: def compute_etud_ue_coef(self, etudid: int, ue: UniteEns) -> float:
"""Détermine le coefficient de l'UE pour cet étudiant. """Détermine le coefficient de l'UE pour cet étudiant.
N'est utilisé que pour l'injection des UE capitalisées dans la N'est utilisé que pour l'injection des UE capitalisées dans la

View File

@ -39,6 +39,7 @@ class ResultatsSemestre(ResultatsCache):
"modimpl_inscr_df", "modimpl_inscr_df",
"modimpls_results", "modimpls_results",
"etud_coef_ue_df", "etud_coef_ue_df",
"moyennes_matieres",
) )
def __init__(self, formsemestre: FormSemestre): def __init__(self, formsemestre: FormSemestre):
@ -57,6 +58,8 @@ class ResultatsSemestre(ResultatsCache):
self.etud_coef_ue_df = None self.etud_coef_ue_df = None
"""coefs d'UE effectifs pour chaque étudiant (pour form. classiques)""" """coefs d'UE effectifs pour chaque étudiant (pour form. classiques)"""
self.validations = None self.validations = None
self.moyennes_matieres = {}
"""Moyennes de matières, si calculées. { matiere_id : Series, index etudid }"""
def compute(self): def compute(self):
"Charge les notes et inscriptions et calcule toutes les moyennes" "Charge les notes et inscriptions et calcule toutes les moyennes"
@ -165,7 +168,6 @@ class ResultatsSemestre(ResultatsCache):
""" """
# Supposant qu'il y a peu d'UE capitalisées, # Supposant qu'il y a peu d'UE capitalisées,
# on va soustraire la moyenne d'UE et ajouter celle de l'UE capitalisée. # on va soustraire la moyenne d'UE et ajouter celle de l'UE capitalisée.
# return # XXX XXX XXX
if not self.validations: if not self.validations:
self.validations = res_sem.load_formsemestre_validations(self.formsemestre) self.validations = res_sem.load_formsemestre_validations(self.formsemestre)
ue_capitalisees = self.validations.ue_capitalisees ue_capitalisees = self.validations.ue_capitalisees
@ -184,7 +186,9 @@ class ResultatsSemestre(ResultatsCache):
sum_coefs_ue = 0.0 sum_coefs_ue = 0.0
for ue in self.formsemestre.query_ues(): for ue in self.formsemestre.query_ues():
ue_cap = self.get_etud_ue_status(etudid, ue.id) ue_cap = self.get_etud_ue_status(etudid, ue.id)
if ue_cap and ue_cap["is_capitalized"]: if ue_cap is None:
continue
if ue_cap["is_capitalized"]:
recompute_mg = True recompute_mg = True
coef = ue_cap["coef_ue"] coef = ue_cap["coef_ue"]
if not np.isnan(ue_cap["moy"]): if not np.isnan(ue_cap["moy"]):
@ -195,6 +199,12 @@ class ResultatsSemestre(ResultatsCache):
# On doit prendre en compte une ou plusieurs UE capitalisées # On doit prendre en compte une ou plusieurs UE capitalisées
# et donc recalculer la moyenne générale # et donc recalculer la moyenne générale
self.etud_moy_gen[etudid] = sum_notes_ue / sum_coefs_ue self.etud_moy_gen[etudid] = sum_notes_ue / sum_coefs_ue
# Ajoute le bonus sport
if self.bonus is not None and self.bonus[etudid]:
self.etud_moy_gen[etudid] += self.bonus[etudid]
self.etud_moy_gen[etudid] = max(
0.0, min(self.etud_moy_gen[etudid], 20.0)
)
def _get_etud_ue_cap(self, etudid, ue): def _get_etud_ue_cap(self, etudid, ue):
"""""" """"""
@ -510,8 +520,9 @@ class NotesTableCompat(ResultatsSemestre):
def get_etud_mat_moy(self, matiere_id, etudid): def get_etud_mat_moy(self, matiere_id, etudid):
"""moyenne d'un étudiant dans une matière (ou NA si pas de notes)""" """moyenne d'un étudiant dans une matière (ou NA si pas de notes)"""
# non supporté en 9.2 if not self.moyennes_matieres:
return "na" return "nd"
return self.moyennes_matieres[matiere_id][etudid]
def get_etud_mod_moy(self, moduleimpl_id: int, etudid: int) -> float: def get_etud_mod_moy(self, moduleimpl_id: int, etudid: int) -> float:
"""La moyenne de l'étudiant dans le moduleimpl """La moyenne de l'étudiant dans le moduleimpl

View File

@ -361,7 +361,7 @@ class FormSemestre(db.Model):
def get_abs_count(self, etudid): def get_abs_count(self, etudid):
"""Les comptes d'absences de cet étudiant dans ce semestre: """Les comptes d'absences de cet étudiant dans ce semestre:
tuple (nb abs non justifiées, nb abs justifiées) tuple (nb abs, nb abs justifiées)
Utilise un cache. Utilise un cache.
""" """
from app.scodoc import sco_abs from app.scodoc import sco_abs

View File

@ -1037,7 +1037,7 @@ def get_abs_count(etudid, sem):
def get_abs_count_in_interval(etudid, date_debut_iso, date_fin_iso): def get_abs_count_in_interval(etudid, date_debut_iso, date_fin_iso):
"""Les comptes d'absences de cet étudiant entre ces deux dates, incluses: """Les comptes d'absences de cet étudiant entre ces deux dates, incluses:
tuple (nb abs non justifiées, nb abs justifiées) tuple (nb abs, nb abs justifiées)
Utilise un cache. Utilise un cache.
""" """
key = str(etudid) + "_" + date_debut_iso + "_" + date_fin_iso key = str(etudid) + "_" + date_debut_iso + "_" + date_fin_iso

View File

@ -65,7 +65,7 @@ from app.scodoc import sco_preferences
from app.scodoc import sco_pvjury from app.scodoc import sco_pvjury
from app.scodoc import sco_users from app.scodoc import sco_users
import app.scodoc.sco_utils as scu import app.scodoc.sco_utils as scu
from app.scodoc.sco_utils import ModuleType from app.scodoc.sco_utils import ModuleType, fmt_note
import app.scodoc.notesdb as ndb import app.scodoc.notesdb as ndb
# ----- CLASSES DE BULLETINS DE NOTES # ----- CLASSES DE BULLETINS DE NOTES
@ -189,7 +189,9 @@ def formsemestre_bulletinetud_dict(formsemestre_id, etudid, version="long"):
formsemestre.etuds_inscriptions[etudid].etat formsemestre.etuds_inscriptions[etudid].etat
) )
I["etud_etat"] = nt.get_etud_etat(etudid) I["etud_etat"] = nt.get_etud_etat(etudid)
I["filigranne"] = sco_bulletins_pdf.get_filigranne(I["etud_etat"], prefs) I["filigranne"] = sco_bulletins_pdf.get_filigranne(
I["etud_etat"], prefs, decision_dem=I["decision_sem"]
)
I["demission"] = "" I["demission"] = ""
if I["etud_etat"] == scu.DEMISSION: if I["etud_etat"] == scu.DEMISSION:
I["demission"] = "(Démission)" I["demission"] = "(Démission)"
@ -197,15 +199,7 @@ def formsemestre_bulletinetud_dict(formsemestre_id, etudid, version="long"):
I["demission"] = "(Défaillant)" I["demission"] = "(Défaillant)"
# --- Appreciations # --- Appreciations
cnx = ndb.GetDBConnexion() I.update(get_appreciations_list(formsemestre_id, etudid))
apprecs = sco_etud.appreciations_list(
cnx, args={"etudid": etudid, "formsemestre_id": formsemestre_id}
)
I["appreciations_list"] = apprecs
I["appreciations_txt"] = [x["date"] + ": " + x["comment"] for x in apprecs]
I["appreciations"] = I[
"appreciations_txt"
] # deprecated / keep it for backward compat in templates
# --- Notes # --- Notes
ues = nt.get_ues_stat_dict() ues = nt.get_ues_stat_dict()
@ -297,7 +291,7 @@ def formsemestre_bulletinetud_dict(formsemestre_id, etudid, version="long"):
else: else:
u["cur_moy_ue_txt"] = "bonus appliqué sur les UEs" u["cur_moy_ue_txt"] = "bonus appliqué sur les UEs"
else: else:
u["cur_moy_ue_txt"] = "bonus de %.3g points" % x u["cur_moy_ue_txt"] = f"bonus de {fmt_note(x)} points"
if nt.bonus_ues is not None: if nt.bonus_ues is not None:
u["cur_moy_ue_txt"] += " (+ues)" u["cur_moy_ue_txt"] += " (+ues)"
u["moy_ue_txt"] = scu.fmt_note(ue_status["moy"]) u["moy_ue_txt"] = scu.fmt_note(ue_status["moy"])
@ -397,6 +391,21 @@ def formsemestre_bulletinetud_dict(formsemestre_id, etudid, version="long"):
return C return C
def get_appreciations_list(formsemestre_id: int, etudid: int) -> dict:
"""Appréciations pour cet étudiant dans ce semestre"""
cnx = ndb.GetDBConnexion()
apprecs = sco_etud.appreciations_list(
cnx, args={"etudid": etudid, "formsemestre_id": formsemestre_id}
)
d = {
"appreciations_list": apprecs,
"appreciations_txt": [x["date"] + ": " + x["comment"] for x in apprecs],
}
# deprecated / keep it for backward compat in templates:
d["appreciations"] = d["appreciations_txt"]
return d
def _get_etud_etat_html(etat: str) -> str: def _get_etud_etat_html(etat: str) -> str:
"""chaine html représentant l'état (backward compat sco7)""" """chaine html représentant l'état (backward compat sco7)"""
if etat == scu.INSCRIT: # "I" if etat == scu.INSCRIT: # "I"
@ -923,7 +932,7 @@ def do_formsemestre_bulletinetud(
if formsemestre.formation.is_apc(): if formsemestre.formation.is_apc():
etud = Identite.query.get(etudid) etud = Identite.query.get(etudid)
r = bulletin_but.BulletinBUT(formsemestre) r = bulletin_but.BulletinBUT(formsemestre)
I = r.bulletin_etud_complet(etud, formsemestre) I = r.bulletin_etud_complet(etud)
else: else:
I = formsemestre_bulletinetud_dict(formsemestre.id, etudid) I = formsemestre_bulletinetud_dict(formsemestre.id, etudid)
etud = I["etud"] etud = I["etud"]

View File

@ -63,41 +63,6 @@ from app.scodoc import sco_pdf
from app.scodoc.sco_pdf import PDFLOCK from app.scodoc.sco_pdf import PDFLOCK
import sco_version import sco_version
# Liste des types des classes de générateurs de bulletins PDF:
BULLETIN_CLASSES = collections.OrderedDict()
def register_bulletin_class(klass):
BULLETIN_CLASSES[klass.__name__] = klass
def bulletin_class_descriptions():
return [x.description for x in BULLETIN_CLASSES.values()]
def bulletin_class_names():
return list(BULLETIN_CLASSES.keys())
def bulletin_default_class_name():
return bulletin_class_names()[0]
def bulletin_get_class(class_name):
return BULLETIN_CLASSES[class_name]
def bulletin_get_class_name_displayed(formsemestre_id):
"""Le nom du générateur utilisé, en clair"""
from app.scodoc import sco_preferences
bul_class_name = sco_preferences.get_preference("bul_class_name", formsemestre_id)
try:
gen_class = bulletin_get_class(bul_class_name)
return gen_class.description
except:
return "invalide ! (voir paramètres)"
class BulletinGenerator: class BulletinGenerator:
"Virtual superclass for PDF bulletin generators" "" "Virtual superclass for PDF bulletin generators" ""
@ -105,6 +70,7 @@ class BulletinGenerator:
# see sco_bulletins_standard.BulletinGeneratorStandard subclass for real methods # see sco_bulletins_standard.BulletinGeneratorStandard subclass for real methods
supported_formats = [] # should list supported formats, eg [ 'html', 'pdf' ] supported_formats = [] # should list supported formats, eg [ 'html', 'pdf' ]
description = "superclass for bulletins" # description for user interface description = "superclass for bulletins" # description for user interface
list_in_menu = True # la classe doit-elle est montrée dans le menu de config ?
def __init__( def __init__(
self, self,
@ -270,9 +236,14 @@ def make_formsemestre_bulletinetud(
formsemestre_id = infos["formsemestre_id"] formsemestre_id = infos["formsemestre_id"]
bul_class_name = sco_preferences.get_preference("bul_class_name", formsemestre_id) bul_class_name = sco_preferences.get_preference("bul_class_name", formsemestre_id)
try:
gen_class = None
if infos.get("type") == "BUT" and format.startswith("pdf"):
gen_class = bulletin_get_class(bul_class_name + "BUT")
if gen_class is None:
gen_class = bulletin_get_class(bul_class_name) gen_class = bulletin_get_class(bul_class_name)
except:
if gen_class is None:
raise ValueError( raise ValueError(
"Type de bulletin PDF invalide (paramètre: %s)" % bul_class_name "Type de bulletin PDF invalide (paramètre: %s)" % bul_class_name
) )
@ -313,3 +284,48 @@ def make_formsemestre_bulletinetud(
filename = bul_generator.get_filename() filename = bul_generator.get_filename()
return data, filename return data, filename
####
# Liste des types des classes de générateurs de bulletins PDF:
BULLETIN_CLASSES = collections.OrderedDict()
def register_bulletin_class(klass):
BULLETIN_CLASSES[klass.__name__] = klass
def bulletin_class_descriptions():
return [x.description for x in BULLETIN_CLASSES.values()]
def bulletin_class_names() -> list[str]:
"Liste les noms des classes de bulletins à présenter à l'utilisateur"
return [
class_name
for class_name in BULLETIN_CLASSES
if BULLETIN_CLASSES[class_name].list_in_menu
]
def bulletin_default_class_name():
return bulletin_class_names()[0]
def bulletin_get_class(class_name: str) -> BulletinGenerator:
"""La class de génération de bulletin de ce nom,
ou None si pas trouvée
"""
return BULLETIN_CLASSES.get(class_name)
def bulletin_get_class_name_displayed(formsemestre_id):
"""Le nom du générateur utilisé, en clair"""
from app.scodoc import sco_preferences
bul_class_name = sco_preferences.get_preference("bul_class_name", formsemestre_id)
gen_class = bulletin_get_class(bul_class_name)
if gen_class is None:
return "invalide ! (voir paramètres)"
return gen_class.description

View File

@ -276,13 +276,13 @@ def get_etud_bulletins_pdf(etudid, version="selectedevals"):
return pdfdoc, filename return pdfdoc, filename
def get_filigranne(etud_etat: str, prefs) -> str: def get_filigranne(etud_etat: str, prefs, decision_sem=None) -> str:
"""Texte à placer en "filigranne" sur le bulletin pdf""" """Texte à placer en "filigranne" sur le bulletin pdf"""
if etud_etat == scu.DEMISSION: if etud_etat == scu.DEMISSION:
return "Démission" return "Démission"
elif etud_etat == sco_codes_parcours.DEF: elif etud_etat == sco_codes_parcours.DEF:
return "Défaillant" return "Défaillant"
elif (prefs["bul_show_temporary"] and not I["decision_sem"]) or prefs[ elif (prefs["bul_show_temporary"] and not decision_sem) or prefs[
"bul_show_temporary_forced" "bul_show_temporary_forced"
]: ]:
return prefs["bul_temporary_txt"] return prefs["bul_temporary_txt"]

View File

@ -66,7 +66,8 @@ from app.scodoc import sco_groups
from app.scodoc import sco_evaluations from app.scodoc import sco_evaluations
from app.scodoc import gen_tables from app.scodoc import gen_tables
# 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) # 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 BulletinGeneratorStandard(sco_bulletins_generator.BulletinGenerator): class BulletinGeneratorStandard(sco_bulletins_generator.BulletinGenerator):
description = "standard ScoDoc (version 2011)" # la description doit être courte: elle apparait dans le menu de paramètrage ScoDoc description = "standard ScoDoc (version 2011)" # la description doit être courte: elle apparait dans le menu de paramètrage ScoDoc
supported_formats = ["html", "pdf"] supported_formats = ["html", "pdf"]
@ -264,11 +265,11 @@ class BulletinGeneratorStandard(sco_bulletins_generator.BulletinGenerator):
def build_bulletin_table(self): def build_bulletin_table(self):
"""Génère la table centrale du bulletin de notes """Génère la table centrale du bulletin de notes
Renvoie: colkeys, P, pdf_style, colWidths Renvoie: col_keys, P, pdf_style, col_widths
- colkeys: nom des colonnes de la table (clés) - col_keys: nom des colonnes de la table (clés)
- table (liste de dicts de chaines de caracteres) - table: liste de dicts de chaines de caractères
- style (commandes table Platypus) - pdf_style: commandes table Platypus
- largeurs de colonnes pour PDF - col_widths: largeurs de colonnes pour PDF
""" """
I = self.infos I = self.infos
P = [] # elems pour générer table avec gen_table (liste de dicts) P = [] # elems pour générer table avec gen_table (liste de dicts)
@ -287,25 +288,25 @@ class BulletinGeneratorStandard(sco_bulletins_generator.BulletinGenerator):
with_col_coef = prefs["bul_show_coef"] with_col_coef = prefs["bul_show_coef"]
with_col_ects = prefs["bul_show_ects"] with_col_ects = prefs["bul_show_ects"]
colkeys = ["titre", "module"] # noms des colonnes à afficher col_keys = ["titre", "module"] # noms des colonnes à afficher
if with_col_rang: if with_col_rang:
colkeys += ["rang"] col_keys += ["rang"]
if with_col_minmax: if with_col_minmax:
colkeys += ["min"] col_keys += ["min"]
if with_col_moypromo: if with_col_moypromo:
colkeys += ["moy"] col_keys += ["moy"]
if with_col_minmax: if with_col_minmax:
colkeys += ["max"] col_keys += ["max"]
colkeys += ["note"] col_keys += ["note"]
if with_col_coef: if with_col_coef:
colkeys += ["coef"] col_keys += ["coef"]
if with_col_ects: if with_col_ects:
colkeys += ["ects"] col_keys += ["ects"]
if with_col_abs: if with_col_abs:
colkeys += ["abs"] col_keys += ["abs"]
colidx = {} # { nom_colonne : indice à partir de 0 } (pour styles platypus) colidx = {} # { nom_colonne : indice à partir de 0 } (pour styles platypus)
i = 0 i = 0
for k in colkeys: for k in col_keys:
colidx[k] = i colidx[k] = i
i += 1 i += 1
@ -313,7 +314,7 @@ class BulletinGeneratorStandard(sco_bulletins_generator.BulletinGenerator):
bul_pdf_mod_colwidth = float(prefs["bul_pdf_mod_colwidth"]) * cm bul_pdf_mod_colwidth = float(prefs["bul_pdf_mod_colwidth"]) * cm
else: else:
bul_pdf_mod_colwidth = None bul_pdf_mod_colwidth = None
colWidths = { col_widths = {
"titre": None, "titre": None,
"module": bul_pdf_mod_colwidth, "module": bul_pdf_mod_colwidth,
"min": 1.5 * cm, "min": 1.5 * cm,
@ -541,7 +542,7 @@ class BulletinGeneratorStandard(sco_bulletins_generator.BulletinGenerator):
("BOX", (0, 0), (-1, -1), 0.4, blue), # ajoute cadre extérieur bleu: ("BOX", (0, 0), (-1, -1), 0.4, blue), # ajoute cadre extérieur bleu:
] ]
# #
return colkeys, P, pdf_style, colWidths return col_keys, P, pdf_style, col_widths
def _list_modules( def _list_modules(
self, self,

View File

@ -595,11 +595,12 @@ def formsemestre_description_table(formsemestre_id, with_evals=False):
"""Description du semestre sous forme de table exportable """Description du semestre sous forme de table exportable
Liste des modules et de leurs coefficients Liste des modules et de leurs coefficients
""" """
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
formsemestre = FormSemestre.query.get_or_404(formsemestre_id) formsemestre = FormSemestre.query.get_or_404(formsemestre_id)
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre) nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
use_ue_coefs = sco_preferences.get_preference("use_ue_coefs", formsemestre_id) use_ue_coefs = sco_preferences.get_preference("use_ue_coefs", formsemestre_id)
F = sco_formations.formation_list(args={"formation_id": sem["formation_id"]})[0] F = sco_formations.formation_list(args={"formation_id": formsemestre.formation_id})[
0
]
parcours = sco_codes_parcours.get_parcours_from_code(F["type_parcours"]) parcours = sco_codes_parcours.get_parcours_from_code(F["type_parcours"])
Mlist = sco_moduleimpl.moduleimpl_withmodule_list( Mlist = sco_moduleimpl.moduleimpl_withmodule_list(
formsemestre_id=formsemestre_id, sort_by_ue=True formsemestre_id=formsemestre_id, sort_by_ue=True
@ -709,7 +710,7 @@ def formsemestre_description_table(formsemestre_id, with_evals=False):
titles["coefficient"] = "Coef. éval." titles["coefficient"] = "Coef. éval."
titles["evalcomplete_str"] = "Complète" titles["evalcomplete_str"] = "Complète"
titles["publish_incomplete_str"] = "Toujours Utilisée" titles["publish_incomplete_str"] = "Toujours Utilisée"
title = "%s %s" % (parcours.SESSION_NAME.capitalize(), sem["titremois"]) title = "%s %s" % (parcours.SESSION_NAME.capitalize(), formsemestre.titre_mois())
return GenTable( return GenTable(
columns_ids=columns_ids, columns_ids=columns_ids,

View File

@ -1,7 +1,7 @@
# -*- mode: python -*- # -*- mode: python -*-
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
SCOVERSION = "9.2a-61" SCOVERSION = "9.2a-62"
SCONAME = "ScoDoc" SCONAME = "ScoDoc"