forked from ScoDoc/ScoDoc
WIP: bulletins BUT pdf
This commit is contained in:
commit
6b8b0f9c24
@ -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:
|
||||||
|
@ -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
116
app/but/bulletin_but_pdf.py
Normal 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
52
app/comp/moy_mat.py
Normal 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
|
@ -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
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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"]
|
||||||
|
@ -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
|
||||||
|
@ -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"]
|
||||||
|
@ -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,
|
||||||
|
@ -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,
|
||||||
|
@ -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"
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user