From 9c0ac1ab48e4e7224263ba2eebb7a11197bbcfe5 Mon Sep 17 00:00:00 2001 From: Emmanuel Viennet Date: Wed, 6 Dec 2023 12:59:30 +0100 Subject: [PATCH 1/4] =?UTF-8?q?Admin.=20comptes=20utilisateurs:=20permet?= =?UTF-8?q?=20au=20non=20super-admin=20de=20choisir=20le=20d=C3=A9partemen?= =?UTF-8?q?t=20de=20rattachement=20si=20il=20a=20le=20droit=20d'administre?= =?UTF-8?q?r=20plusieurs=20depts.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/views/users.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/app/views/users.py b/app/views/users.py index 143a5e4c7..796e0f852 100644 --- a/app/views/users.py +++ b/app/views/users.py @@ -488,7 +488,8 @@ def create_user_form(user_name=None, edit=0, all_roles=True): }, ), ] - # Si SuperAdmin, propose de choisir librement le dept du nouvel utilisateur + # Si on a le droit d'administrer les utilisateurs de plusieurs départements, + # propose le choix du dept du nouvel utilisateur selectable_dept_acronyms = set(administrable_dept_acronyms) if edit: if the_user.dept is not None: # ajoute dept actuel de l'utilisateur @@ -500,7 +501,7 @@ def create_user_form(user_name=None, edit=0, all_roles=True): if g.scodoc_dept in selectable_dept_acronyms else (auth_dept or "") ) - if is_super_admin and len(selectable_dept_acronyms) > 1: + if len(selectable_dept_acronyms) > 1: selectable_dept_acronyms = sorted(list(selectable_dept_acronyms)) descr.append( ( @@ -685,9 +686,8 @@ def create_user_form(user_name=None, edit=0, all_roles=True): if "status" in vals: vals["active"] = vals["status"] == "" # Département: - if auth_dept: # pas super-admin - if ("dept" in vals) and (vals["dept"] not in selectable_dept_acronyms): - del vals["dept"] # ne change pas de dept + if ("dept" in vals) and (vals["dept"] not in selectable_dept_acronyms): + del vals["dept"] # ne change pas de dept # Traitement des roles: ne doit pas affecter les rôles # que l'on en contrôle pas: for role in orig_roles_strings: # { "Ens_RT", "Secr_CJ", ... } From 78fbaf1ac870dec86b7297910af24dd6622f7e53 Mon Sep 17 00:00:00 2001 From: Emmanuel Viennet Date: Wed, 6 Dec 2023 18:57:07 +0100 Subject: [PATCH 2/4] =?UTF-8?q?Fix:=20pr=C3=A9cision=20affichage=20sommes?= =?UTF-8?q?=20coefs=20BUT?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/static/js/table_editor.js | 20 ++++++++++++++++---- app/templates/pn/form_modules_ue_coefs.j2 | 10 +++++----- 2 files changed, 21 insertions(+), 9 deletions(-) diff --git a/app/static/js/table_editor.js b/app/static/js/table_editor.js index 18b4bd686..6cb410e6a 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 01ee098ac..66ba286fc 100644 --- a/app/templates/pn/form_modules_ue_coefs.j2 +++ b/app/templates/pn/form_modules_ue_coefs.j2 @@ -33,8 +33,8 @@ {% endif %}
- revenir à la formation
@@ -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.

@@ -103,7 +103,7 @@ obj.classList.add("modified"); else obj.classList.remove("modified"); - // Lorsque les données sont bien enregistrées, on enlève + // Lorsque les données sont bien enregistrées, on enlève // l'indication que c'est bon au bout d'un temps //setTimeout(() => { // obj.classList.remove("modified"); @@ -112,4 +112,4 @@ ); return true; } - \ No newline at end of file + From 0f860f912c70bc1f7193a5156389217e99d94332 Mon Sep 17 00:00:00 2001 From: Emmanuel Viennet Date: Wed, 6 Dec 2023 20:04:40 +0100 Subject: [PATCH 3/4] Classeur PDF des bulletins BUT courts --- app/but/bulletin_but_court.py | 48 ++++++++++++++++++++++--------- app/but/bulletin_but_court_pdf.py | 37 ++++++++++++++---------- app/scodoc/sco_bulletins_pdf.py | 29 ++++++++++++------- app/scodoc/sco_cache.py | 2 +- app/scodoc/sco_recapcomplet.py | 17 +++++++++-- app/views/notes.py | 20 ++++++------- 6 files changed, 99 insertions(+), 54 deletions(-) diff --git a/app/but/bulletin_but_court.py b/app/but/bulletin_but_court.py index a4b5dabd0..59f77b20b 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,38 @@ 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) -> 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=False) + + +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 +138,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 f1fd112ad..7e9e7e81d 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_bulletins_pdf.py b/app/scodoc/sco_bulletins_pdf.py index 5ccb6c9f3..658bcf50f 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,33 @@ 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("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 245389ba5..c80049b1d 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 5560f2137..7ddcbcc9c 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/views/notes.py b/app/views/notes.py index c7a4613b0..c6f8f5482 100644 --- a/app/views/notes.py +++ b/app/views/notes.py @@ -271,9 +271,7 @@ sco_publish( ) -@bp.route( - "/formsemestre_bulletinetud", methods=["GET", "POST"] -) # POST pour compat anciens clients PHP (deprecated) +@bp.route("/formsemestre_bulletinetud") @scodoc @permission_required_compat_scodoc7(Permission.ScoView) @scodoc7func @@ -790,7 +788,7 @@ def formation_import_xml_form():
  • Supprimer cette formation + )}">Supprimer cette formation (en cas d'erreur, par exemple pour charger auparavant le référentiel de compétences)
  • @@ -987,7 +985,7 @@ def edit_enseignants_form(moduleimpl_id):

    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 +1198,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 +1690,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

    @@ -1727,7 +1725,7 @@ def evaluation_delete(evaluation_id): "\n".join(H) + f"""

    OK, évaluation supprimée.

    Continuer

    """ + html_sco_header.sco_footer() @@ -1863,8 +1861,6 @@ sco_publish( @scodoc7func def formsemestre_bulletins_pdf(formsemestre_id, version="selectedevals"): "Publie les bulletins dans un classeur PDF" - if version not in scu.BULLETINS_VERSIONS: - raise ScoValueError("version de bulletin demandée invalide") pdfdoc, filename = sco_bulletins_pdf.get_formsemestre_bulletins_pdf( formsemestre_id, version=version ) @@ -2499,7 +2495,7 @@ def formsemestre_validation_but( enregistrer des UEs antérieures """ From 689b8610bfc5c5addf40417c48b3ecfc6dc3effa Mon Sep 17 00:00:00 2001 From: Emmanuel Viennet Date: Wed, 6 Dec 2023 20:40:55 +0100 Subject: [PATCH 4/4] Envoi par mail des bulletins PDF courts --- app/but/bulletin_but.py | 4 ++-- app/but/bulletin_but_court.py | 8 +++++-- app/scodoc/sco_archives.py | 6 +++-- app/scodoc/sco_bulletins.py | 24 ++++++++++++++----- app/scodoc/sco_bulletins_pdf.py | 4 +++- app/views/notes.py | 41 ++++++++++++++++++++------------- 6 files changed, 58 insertions(+), 29 deletions(-) diff --git a/app/but/bulletin_but.py b/app/but/bulletin_but.py index 8bdab7b48..e25074ad1 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 59f77b20b..9010ae350 100644 --- a/app/but/bulletin_but_court.py +++ b/app/but/bulletin_but_court.py @@ -83,12 +83,16 @@ def bulletin_but(formsemestre_id: int, etudid: int = None, fmt="html"): ) -def bulletin_but_court_pdf_frag(etud: Identite, formsemestre: FormSemestre) -> bytes: +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=False) + return bulletin_but_court_pdf.make_bulletin_but_court_pdf( + args, stand_alone=stand_alone + ) def _build_bulletin_but_infos( diff --git a/app/scodoc/sco_archives.py b/app/scodoc/sco_archives.py index 05e75f1f1..8d91bbcf1 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 e0b64eb5c..82d8da5aa 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 658bcf50f..cf0d1348c 100644 --- a/app/scodoc/sco_bulletins_pdf.py +++ b/app/scodoc/sco_bulletins_pdf.py @@ -222,7 +222,9 @@ def get_formsemestre_bulletins_pdf(formsemestre_id, version="selectedevals"): else scu.BULLETINS_VERSIONS ) if version not in versions: - raise ScoValueError("version de bulletin demandée invalide !") + 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] diff --git a/app/views/notes.py b/app/views/notes.py index c6f8f5482..b7fbbd915 100644 --- a/app/views/notes.py +++ b/app/views/notes.py @@ -288,7 +288,9 @@ def formsemestre_bulletinetud( ): fmt = fmt or "html" if version not in scu.BULLETINS_VERSIONS_BUT: - raise ScoValueError("version de bulletin demandée invalide") + raise ScoValueError( + "formsemestre_bulletinetud: version de bulletin demandée invalide" + ) if not isinstance(etudid, int): raise ScoInvalidIdType("formsemestre_bulletinetud: etudid must be an integer !") if formsemestre_id is not None and not isinstance(formsemestre_id, int): @@ -1881,13 +1883,14 @@ _EXPL_BULL = """Versions des bulletins: @scodoc7func def formsemestre_bulletins_pdf_choice(formsemestre_id, version=None): """Choix version puis envoi classeur bulletins pdf""" + formsemestre = FormSemestre.get_formsemestre(formsemestre_id) if version: pdfdoc, filename = sco_bulletins_pdf.get_formsemestre_bulletins_pdf( formsemestre_id, version=version ) return scu.sendPDFFile(pdfdoc, filename) - return formsemestre_bulletins_choice( - formsemestre_id, + return _formsemestre_bulletins_choice( + formsemestre, title="Choisir la version des bulletins à générer", explanation=_EXPL_BULL, ) @@ -1900,7 +1903,7 @@ def formsemestre_bulletins_pdf_choice(formsemestre_id, version=None): def etud_bulletins_pdf(etudid, version="selectedevals"): "Publie tous les bulletins d'un etudiants dans un classeur PDF" if version not in scu.BULLETINS_VERSIONS: - raise ScoValueError("version de bulletin demandée invalide") + raise ScoValueError("etud_bulletins_pdf: version de bulletin demandée invalide") pdfdoc, filename = sco_bulletins_pdf.get_etud_bulletins_pdf(etudid, version=version) return scu.sendPDFFile(pdfdoc, filename) @@ -1927,15 +1930,20 @@ def formsemestre_bulletins_mailetuds_choice( prefer_mail_perso=prefer_mail_perso, ) ) - + formsemestre = FormSemestre.get_formsemestre(formsemestre_id) expl_bull = """Versions des bulletins: