diff --git a/app/but/bulletin_but.py b/app/but/bulletin_but.py index 8bdab7b48f..e25074ad1d 100644 --- a/app/but/bulletin_but.py +++ b/app/but/bulletin_but.py @@ -345,8 +345,8 @@ class BulletinBUT: - Si force_publishing, rempli le bulletin même si bul_hide_xml est vrai (bulletins non publiés). """ - if version not in scu.BULLETINS_VERSIONS: - raise ScoValueError("version de bulletin demandée invalide") + if version not in scu.BULLETINS_VERSIONS_BUT: + raise ScoValueError("bulletin_etud: version de bulletin demandée invalide") res = self.res formsemestre = res.formsemestre etat_inscription = etud.inscription_etat(formsemestre.id) diff --git a/app/but/bulletin_but_court.py b/app/but/bulletin_but_court.py index a4b5dabd07..9010ae350f 100644 --- a/app/but/bulletin_but_court.py +++ b/app/but/bulletin_but_court.py @@ -18,7 +18,7 @@ Ces données sont des objets passés au template. - `bul: dict` : le bulletin (dict, même structure que le json publié) - `cursus: EtudCursusBUT`: infos sur le cursus BUT (niveaux validés etc) - `decision_ues: dict`: `{ acronyme_ue : { 'code' : 'ADM' }}` accès aux décisions - de jury d'UE + de jury d'UE - `ects_total` : nombre d'ECTS validées dans ce cursus - `ue_validation_by_niveau : dict` : les validations d'UE de chaque niveau du cursus """ @@ -65,6 +65,42 @@ def bulletin_but(formsemestre_id: int, etudid: int = None, fmt="html"): ) if not formsemestre.formation.is_apc(): raise ScoValueError("formation non BUT") + + args = _build_bulletin_but_infos(etud, formsemestre, fmt=fmt) + + if fmt == "pdf": + filename = scu.bul_filename(formsemestre, etud, prefix="bul-but") + bul_pdf = bulletin_but_court_pdf.make_bulletin_but_court_pdf(args) + return scu.sendPDFFile(bul_pdf, filename + ".pdf") + + return render_template( + "but/bulletin_court_page.j2", + datetime=datetime, + sco=ScoData(formsemestre=formsemestre, etud=etud), + time=time, + version="butcourt", + **args, + ) + + +def bulletin_but_court_pdf_frag( + etud: Identite, formsemestre: FormSemestre, stand_alone=False +) -> bytes: + """Le code PDF d'un bulletin BUT court, à intégrer dans un document + (pour les classeurs de tous les bulletins) + """ + args = _build_bulletin_but_infos(etud, formsemestre) + return bulletin_but_court_pdf.make_bulletin_but_court_pdf( + args, stand_alone=stand_alone + ) + + +def _build_bulletin_but_infos( + etud: Identite, formsemestre: FormSemestre, fmt="pdf" +) -> dict: + """Réuni toutes les information pour le contenu d'un bulletin BUT court. + On indique le format ("html" ou "pdf") car il y a moins d'infos en HTML. + """ bulletins_sem = BulletinBUT(formsemestre) if fmt == "pdf": bul: dict = bulletins_sem.bulletin_etud_complet(etud) @@ -106,16 +142,4 @@ def bulletin_but(formsemestre_id: int, etudid: int = None, fmt="html"): if ue.type == UE_STANDARD and ue.acronyme in ue_acronyms ], } - if fmt == "pdf": - filename = scu.bul_filename(formsemestre, etud, prefix="bul-but") - bul_pdf = bulletin_but_court_pdf.make_bulletin_but_court_pdf(**args) - return scu.sendPDFFile(bul_pdf, filename + ".pdf") - - return render_template( - "but/bulletin_court_page.j2", - datetime=datetime, - sco=ScoData(formsemestre=formsemestre, etud=etud), - time=time, - version="butcourt", - **args, - ) + return args diff --git a/app/but/bulletin_but_court_pdf.py b/app/but/bulletin_but_court_pdf.py index f1fd112ad9..7e9e7e81de 100644 --- a/app/but/bulletin_but_court_pdf.py +++ b/app/but/bulletin_but_court_pdf.py @@ -6,7 +6,7 @@ """Génération bulletin BUT PDF synthétique en une page -On génère du PDF avec reportLab en utilisant les classes +On génère du PDF avec reportLab en utilisant les classes ScoDoc BulletinGenerator et GenTable. """ @@ -34,25 +34,32 @@ from app.scodoc.sco_preferences import SemPreferences def make_bulletin_but_court_pdf( - bul: dict = None, - cursus: cursus_but.EtudCursusBUT = None, - decision_ues: dict = None, - ects_total: float = 0.0, - etud: Identite = None, - formsemestre: FormSemestre = None, - logo: Logo = None, - prefs: SemPreferences = None, - title: str = "", - ue_validation_by_niveau: dict[tuple[int, str], ScolarFormSemestreValidation] = None, - ues_acronyms: list[str] = None, + args: dict, + stand_alone: bool = True, ) -> bytes: - "génère le bulletin court BUT en pdf" + """génère le bulletin court BUT en pdf. + Si stand_alone, génère un doc pdf complet (une page ici), + sinon un morceau (fragment) à intégrer dans un autre document. + + args donne toutes les infos du contenu du bulletin: + bul: dict = None, + cursus: cursus_but.EtudCursusBUT = None, + decision_ues: dict = None, + ects_total: float = 0.0, + etud: Identite = None, + formsemestre: FormSemestre = None, + logo: Logo = None, + prefs: SemPreferences = None, + title: str = "", + ue_validation_by_niveau: dict[tuple[int, str], ScolarFormSemestreValidation] = None, + ues_acronyms: list[str] = None, + """ # A priori ce verrou n'est plus nécessaire avec Flask (multi-process) # mais... try: PDFLOCK.acquire() - bul_generator = BulletinGeneratorBUTCourt(**locals()) - bul_pdf = bul_generator.generate(fmt="pdf") + bul_generator = BulletinGeneratorBUTCourt(**args) + bul_pdf = bul_generator.generate(fmt="pdf", stand_alone=stand_alone) finally: PDFLOCK.release() return bul_pdf diff --git a/app/scodoc/sco_archives.py b/app/scodoc/sco_archives.py index 05e75f1f1c..8d91bbcf1c 100644 --- a/app/scodoc/sco_archives.py +++ b/app/scodoc/sco_archives.py @@ -346,7 +346,9 @@ def do_formsemestre_archive( ) if bul_version not in scu.BULLETINS_VERSIONS: - raise ScoValueError("version de bulletin demandée invalide") + raise ScoValueError( + "do_formsemestre_archive: version de bulletin demandée invalide" + ) formsemestre = FormSemestre.get_formsemestre(formsemestre_id) res: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre) sem_archive_id = formsemestre_id @@ -505,7 +507,7 @@ enregistrés et non modifiables, on peut les retrouver ultérieurement. """, ] F = [ - f"""
Note: les documents sont aussi affectés par les réglages sur la page
+ f""" Note: les documents sont aussi affectés par les réglages sur la page
"Paramétrage"
diff --git a/app/scodoc/sco_bulletins.py b/app/scodoc/sco_bulletins.py
index e0b64eb5c0..82d8da5aa6 100644
--- a/app/scodoc/sco_bulletins.py
+++ b/app/scodoc/sco_bulletins.py
@@ -513,8 +513,8 @@ def _ue_mod_bulletin(
sco_users.user_info(modimpl["responsable_id"])["nomcomplet"],
)
link_mod = f""""""
@@ -576,7 +576,7 @@ def _ue_mod_bulletin(
"name"
] = f"""{e.description or ""} {
e.descr_date()
- if e.date_debut and not is_complete
+ if e.date_debut and not is_complete
else ""}"""
e_dict["target_html"] = url_for(
"notes.evaluation_listenotes",
@@ -985,6 +985,8 @@ def do_formsemestre_bulletinetud(
où bul est str ou bytes au format demandé (html, pdf, pdfmail, pdfpart, xml, json)
et filigranne est un message à placer en "filigranne" (eg "Provisoire").
"""
+ from app.but import bulletin_but_court
+
fmt = fmt or "html"
if fmt == "xml":
bul = sco_bulletins_xml.make_xml_formsemestre_bulletinetud(
@@ -1045,9 +1047,19 @@ def do_formsemestre_bulletinetud(
if not can_send_bulletin_by_mail(formsemestre.id):
raise AccessDenied("Vous n'avez pas le droit d'effectuer cette opération !")
- pdfdata, filename = sco_bulletins_generator.make_formsemestre_bulletin_etud(
- bul_dict, etud=etud, formsemestre=formsemestre, version=version, fmt="pdf"
- )
+ if version == "butcourt":
+ pdfdata = bulletin_but_court.bulletin_but_court_pdf_frag(
+ etud, formsemestre, stand_alone=True
+ )
+ filename = scu.bul_filename(formsemestre, etud, prefix="bul-court")
+ else:
+ pdfdata, filename = sco_bulletins_generator.make_formsemestre_bulletin_etud(
+ bul_dict,
+ etud=etud,
+ formsemestre=formsemestre,
+ version=version,
+ fmt="pdf",
+ )
if prefer_mail_perso:
recipient_addr = (
diff --git a/app/scodoc/sco_bulletins_pdf.py b/app/scodoc/sco_bulletins_pdf.py
index 5ccb6c9f34..cf0d1348cb 100644
--- a/app/scodoc/sco_bulletins_pdf.py
+++ b/app/scodoc/sco_bulletins_pdf.py
@@ -61,7 +61,6 @@ from flask import g, request
from app import log, ScoValueError
from app.models import FormSemestre, Identite
-
from app.scodoc import sco_cache
from app.scodoc import codes_cursus
from app.scodoc import sco_pdf
@@ -213,23 +212,35 @@ def process_field(
def get_formsemestre_bulletins_pdf(formsemestre_id, version="selectedevals"):
"Document pdf avec tous les bulletins du semestre, et filename"
+ from app.but import bulletin_but_court
from app.scodoc import sco_bulletins
- if version not in scu.BULLETINS_VERSIONS:
- raise ScoValueError("version de bulletin demandée invalide")
+ formsemestre: FormSemestre = FormSemestre.query.get_or_404(formsemestre_id)
+ versions = (
+ scu.BULLETINS_VERSIONS_BUT
+ if formsemestre.formation.is_apc()
+ else scu.BULLETINS_VERSIONS
+ )
+ if version not in versions:
+ raise ScoValueError(
+ "get_formsemestre_bulletins_pdf: version de bulletin demandée invalide !"
+ )
cached = sco_cache.SemBulletinsPDFCache.get(str(formsemestre_id) + "_" + version)
if cached:
return cached[1], cached[0]
fragments = []
# Make each bulletin
- formsemestre: FormSemestre = FormSemestre.query.get_or_404(formsemestre_id)
+
for etud in formsemestre.get_inscrits(include_demdef=True, order=True):
- frag, _ = sco_bulletins.do_formsemestre_bulletinetud(
- formsemestre,
- etud,
- fmt="pdfpart",
- version=version,
- )
+ if version == "butcourt":
+ frag = bulletin_but_court.bulletin_but_court_pdf_frag(etud, formsemestre)
+ else:
+ frag, _ = sco_bulletins.do_formsemestre_bulletinetud(
+ formsemestre,
+ etud,
+ fmt="pdfpart",
+ version=version,
+ )
fragments += frag
#
infos = {"DeptName": sco_preferences.get_preference("DeptName", formsemestre_id)}
diff --git a/app/scodoc/sco_cache.py b/app/scodoc/sco_cache.py
index 245389ba5e..c80049b1d5 100644
--- a/app/scodoc/sco_cache.py
+++ b/app/scodoc/sco_cache.py
@@ -190,7 +190,7 @@ class SemBulletinsPDFCache(ScoDocCache):
@classmethod
def invalidate_sems(cls, formsemestre_ids):
"""Clear cached pdf for all given formsemestres"""
- for version in scu.BULLETINS_VERSIONS:
+ for version in scu.BULLETINS_VERSIONS_BUT:
oids = [
str(formsemestre_id) + "_" + version
for formsemestre_id in formsemestre_ids
diff --git a/app/scodoc/sco_recapcomplet.py b/app/scodoc/sco_recapcomplet.py
index 5560f21371..7ddcbcc9c9 100644
--- a/app/scodoc/sco_recapcomplet.py
+++ b/app/scodoc/sco_recapcomplet.py
@@ -157,11 +157,24 @@ def formsemestre_recapcomplet(
H.append(f'')
H.append(
f"""
- (cliquer sur un nom pour afficher son bulletin ou
+ cliquer sur un nom pour afficher son bulletin ou
ici avoir le classeur papier)
+ }">ici avoir le classeur pdf
+ """
+ )
+ if formsemestre.formation.is_apc():
+ H.append(
+ f""" ou en version courte BUT
+ """
+ )
+
+ H.append(
+ """
"""
)
diff --git a/app/static/js/table_editor.js b/app/static/js/table_editor.js
index 18b4bd6865..6cb410e6ac 100644
--- a/app/static/js/table_editor.js
+++ b/app/static/js/table_editor.js
@@ -69,7 +69,10 @@ function showSums(sumsRessources, sumsUE) {
--nbX:1;
--nbY:1;
">
- ${value / 100}
+ ${(value / 100).toLocaleString(undefined, {
+ minimumFractionDigits: 0,
+ maximumFractionDigits: 2,
+ })}
`;
});
@@ -86,7 +89,10 @@ function showSums(sumsRessources, sumsUE) {
--nbX:1;
--nbY:1;
">
- ${value / 100}
+ ${(value / 100).toLocaleString(undefined, {
+ minimumFractionDigits: 0,
+ maximumFractionDigits: 2,
+ })}
`;
});
@@ -223,7 +229,10 @@ function processSums() {
});
document.querySelector(
`.sums[data-x="${this.dataset.x}"][data-y="${lastY}"]`
- ).innerText = sum / 100;
+ ).innerText = (sum / 100).toLocaleString(undefined, {
+ minimumFractionDigits: 0,
+ maximumFractionDigits: 2,
+ });
sum = 0;
document
@@ -238,7 +247,10 @@ function processSums() {
});
document.querySelector(
`.sums[data-x="${lastX}"][data-y="${this.dataset.y}"]`
- ).innerText = sum / 100;
+ ).innerText = (sum / 100).toLocaleString(undefined, {
+ minimumFractionDigits: 0,
+ maximumFractionDigits: 2,
+ });
}
/******************************/
diff --git a/app/templates/pn/form_modules_ue_coefs.j2 b/app/templates/pn/form_modules_ue_coefs.j2
index 01ee098acf..66ba286fc0 100644
--- a/app/templates/pn/form_modules_ue_coefs.j2
+++ b/app/templates/pn/form_modules_ue_coefs.j2
@@ -33,8 +33,8 @@
{% endif %}
@@ -52,7 +52,7 @@
restreindre l'affichage aux UE et modules de l'un des parcours à l'aide du
menu "Parcours" au dessus du tableau. Les UEs et modules de tronc commun
apparaissent toujours. Les cases grisées à droite et en bas donnent la somme des coefficients. Les cases grisées à droite et en bas donnent la somme indicative des coefficients. Pour changer le responsable du module, passez par la
page "Modification du semestre",
accessible uniquement au responsable de la formation (chef de département)
@@ -1200,7 +1200,7 @@ def view_module_abs(moduleimpl_id, fmt="html"):
H = [
html_sco_header.html_sem_header(
f"""Absences du module {modimpl.module.titre_str()}""",
page_title=f"Absences du module {modimpl.module.titre_str()}",
@@ -1692,8 +1692,8 @@ def evaluation_delete(evaluation_id):
if etat["nb_notes"]:
H.append(
f""" Suppression impossible (effacer les notes d'abord) retour au tableau de bord du module
OK, évaluation supprimée.
"""
- return formsemestre_bulletins_choice(
- formsemestre_id,
+ """
+ if formsemestre.formation.is_apc():
+ expl_bull += """
+
"
+ return _formsemestre_bulletins_choice(
+ formsemestre,
title="Choisir la version des bulletins à envoyer par mail",
explanation="""Chaque étudiant (non démissionnaire ni défaillant)
ayant une adresse mail connue de ScoDoc
@@ -1951,23 +1955,24 @@ def formsemestre_bulletins_mailetuds_choice(
# not published
-def formsemestre_bulletins_choice(
- formsemestre_id, title="", explanation="", choose_mail=False
+def _formsemestre_bulletins_choice(
+ formsemestre: FormSemestre, title="", explanation="", choose_mail=False
):
"""Choix d'une version de bulletin"""
+ versions = (
+ scu.BULLETINS_VERSIONS_BUT
+ if formsemestre.formation.is_apc()
+ else scu.BULLETINS_VERSIONS
+ )
H = [
html_sco_header.html_sem_header(title),
f"""