ScoDoc/app/scodoc/sco_bulletins.py

1342 lines
50 KiB
Python
Raw Normal View History

2020-09-26 16:19:37 +02:00
# -*- mode: python -*-
# -*- coding: utf-8 -*-
##############################################################################
#
# Gestion scolarite IUT
#
2023-12-31 23:04:06 +01:00
# Copyright (c) 1999 - 2024 Emmanuel Viennet. All rights reserved.
2020-09-26 16:19:37 +02:00
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
# Emmanuel Viennet emmanuel.viennet@viennet.net
#
##############################################################################
"""Génération des bulletins de notes
"""
import collections
import email
2022-02-14 23:21:42 +01:00
import time
import numpy as np
from flask import g, request, Response
from flask import flash, render_template, url_for
from flask_json import json_response
from flask_login import current_user
2020-09-26 16:19:37 +02:00
from app import db, email
2021-08-29 19:57:32 +02:00
from app import log
from app.scodoc.sco_utils import json_error
2022-02-14 23:21:42 +01:00
from app.but import bulletin_but
2022-01-16 23:47:52 +01:00
from app.comp import res_sem
from app.comp.res_but import ResultatsSemestreBUT
from app.comp.res_compat import NotesTableCompat
from app.models import (
ApcParcours,
2023-08-25 17:58:57 +02:00
Evaluation,
Formation,
FormSemestre,
Identite,
ModuleImplInscription,
ScoDocSiteConfig,
)
from app.scodoc.sco_permissions import Permission
2024-02-22 17:39:18 +01:00
from app.scodoc.sco_exceptions import AccessDenied, ScoValueError, ScoTemporaryError
from app.scodoc import html_sco_header
from app.scodoc import htmlutils
2023-06-30 15:34:50 +02:00
from app.scodoc import sco_assiduites
from app.scodoc import sco_bulletins_generator
from app.scodoc import sco_bulletins_json
2022-02-14 23:21:42 +01:00
from app.scodoc import sco_bulletins_pdf
from app.scodoc import sco_bulletins_xml
from app.scodoc import codes_cursus
2021-07-05 23:41:22 +02:00
from app.scodoc import sco_etud
from app.scodoc import sco_formsemestre
from app.scodoc import sco_groups
from app.scodoc import sco_preferences
from app.scodoc import sco_pv_dict
from app.scodoc import sco_pv_lettres_inviduelles
2021-07-05 23:41:22 +02:00
from app.scodoc import sco_users
2022-02-14 23:21:42 +01:00
import app.scodoc.sco_utils as scu
2022-02-21 19:25:38 +01:00
from app.scodoc.sco_utils import ModuleType, fmt_note
2022-02-14 23:21:42 +01:00
import app.scodoc.notesdb as ndb
2020-09-26 16:19:37 +02:00
def get_formsemestre_bulletin_etud_json(
formsemestre: FormSemestre,
etud: Identite,
force_publishing=False,
version="long",
) -> Response:
"""Le JSON du bulletin d'un étudiant, quel que soit le type de formation."""
if formsemestre.formation.is_apc():
bulletins_sem = bulletin_but.BulletinBUT(formsemestre)
if not etud.id in bulletins_sem.res.identdict:
2022-08-08 10:34:13 +02:00
return json_error(404, "get_formsemestre_bulletin_etud_json: invalid etud")
return json_response(
data_=bulletins_sem.bulletin_etud(
etud,
force_publishing=force_publishing,
version=version,
)
)
return formsemestre_bulletinetud(
etud,
formsemestre_id=formsemestre.id,
fmt="json",
version=version,
xml_with_decisions=True,
force_publishing=force_publishing,
)
# -------------
2022-03-05 12:47:08 +01:00
def make_context_dict(formsemestre: FormSemestre, etud: dict) -> dict:
2020-09-26 16:19:37 +02:00
"""Construit dictionnaire avec valeurs pour substitution des textes
(preferences bul_pdf_*)
"""
2022-03-05 12:47:08 +01:00
C = formsemestre.get_infos_dict()
C["responsable"] = formsemestre.responsables_str()
C["anneesem"] = C["annee"] # backward compat
2020-09-26 16:19:37 +02:00
C.update(etud)
# copie preferences
for name in sco_preferences.get_base_preferences().prefs_name:
2022-03-05 12:47:08 +01:00
C[name] = sco_preferences.get_preference(name, formsemestre.id)
2020-09-26 16:19:37 +02:00
# ajoute groupes et group_0, group_1, ...
2022-03-05 12:47:08 +01:00
sco_groups.etud_add_group_infos(etud, formsemestre.id)
2020-09-26 16:19:37 +02:00
C["groupes"] = etud["groupes"]
n = 0
for partition_id in etud["partitions"]:
C["group_%d" % n] = etud["partitions"][partition_id]["group_name"]
n += 1
# ajoute date courante
t = time.localtime()
C["date_dmy"] = time.strftime(scu.DATE_FMT, t)
2020-09-26 16:19:37 +02:00
C["date_iso"] = time.strftime("%Y-%m-%d", t)
return C
def formsemestre_bulletinetud_dict(formsemestre_id, etudid, version="long"):
2020-09-26 16:19:37 +02:00
"""Collecte informations pour bulletin de notes
Retourne un dictionnaire (avec valeur par défaut chaine vide).
Le contenu du dictionnaire dépend des options (rangs, ...)
2020-09-26 16:19:37 +02:00
et de la version choisie (short, long, selectedevals).
2022-03-05 12:47:08 +01:00
Cette fonction est utilisée pour les bulletins CLASSIQUES (DUT, ...)
en HTML et PDF, mais pas ceux en XML.
2020-09-26 16:19:37 +02:00
"""
2023-06-30 15:34:50 +02:00
from app.scodoc import sco_assiduites
if version not in scu.BULLETINS_VERSIONS:
2020-09-26 16:19:37 +02:00
raise ValueError("invalid version code !")
prefs = sco_preferences.SemPreferences(formsemestre_id)
formsemestre = FormSemestre.get_formsemestre(formsemestre_id)
2022-02-06 16:09:17 +01:00
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
if not nt.get_etud_etat(etudid):
raise ScoValueError("Étudiant non inscrit à ce semestre")
I = collections.defaultdict(str)
2020-09-26 16:19:37 +02:00
I["etudid"] = etudid
I["formsemestre_id"] = formsemestre_id
2022-02-09 00:36:50 +01:00
I["sem"] = formsemestre.get_infos_dict()
I["server_name"] = request.url_root
2020-09-26 16:19:37 +02:00
# Formation et parcours
if I["sem"]["formation_id"]:
formation_dict = Formation.query.get_or_404(I["sem"]["formation_id"]).to_dict()
else: # what's the fuck ?
formation_dict = {
"acronyme": "?",
"code_specialite": "",
"dept_id": 1,
"formation_code": "?",
"formation_id": -1,
"id": -1,
"referentiel_competence_id": None,
"titre": "?",
"titre_officiel": "?",
"type_parcours": 0,
"version": 0,
}
I["formation"] = formation_dict
I["parcours"] = codes_cursus.get_cursus_from_code(I["formation"]["type_parcours"])
2020-09-26 16:19:37 +02:00
# Infos sur l'etudiant
2021-08-22 13:24:36 +02:00
I["etud"] = sco_etud.get_etud_info(etudid=etudid, filled=True)[0]
2020-09-26 16:19:37 +02:00
I["descr_situation"] = I["etud"]["inscriptionstr"]
if I["etud"]["inscription_formsemestre_id"]:
I[
"descr_situation_html"
2021-08-15 21:33:47 +02:00
] = f"""<a href="{url_for(
"notes.formsemestre_status",
scodoc_dept=g.scodoc_dept,
formsemestre_id=I["etud"]["inscription_formsemestre_id"],
)}">{I["descr_situation"]}</a>"""
2020-09-26 16:19:37 +02:00
else:
I["descr_situation_html"] = I["descr_situation"]
# Groupes:
2021-08-19 10:28:35 +02:00
partitions = sco_groups.get_partitions_list(formsemestre_id, with_default=False)
2020-09-26 16:19:37 +02:00
partitions_etud_groups = {} # { partition_id : { etudid : group } }
for partition in partitions:
pid = partition["partition_id"]
2021-08-19 10:28:35 +02:00
partitions_etud_groups[pid] = sco_groups.get_etud_groups_in_partition(pid)
2020-09-26 16:19:37 +02:00
# --- Absences
2024-03-01 12:40:05 +01:00
_, I["nbabsjust"], I["nbabs"] = sco_assiduites.get_assiduites_count(etudid, nt.sem)
2020-09-26 16:19:37 +02:00
# --- Decision Jury
infos, dpv = etud_descr_situation_semestre(
etudid,
formsemestre,
fmt="html",
2020-09-26 16:19:37 +02:00
show_date_inscr=prefs["bul_show_date_inscr"],
show_decisions=prefs["bul_show_decision"],
show_uevalid=prefs["bul_show_uevalid"],
show_mention=prefs["bul_show_mention"],
)
I.update(infos)
2022-01-16 23:47:52 +01:00
I["etud_etat_html"] = _get_etud_etat_html(
formsemestre.etuds_inscriptions[etudid].etat
)
2020-09-26 16:19:37 +02:00
I["etud_etat"] = nt.get_etud_etat(etudid)
2022-02-21 19:25:38 +01:00
I["filigranne"] = sco_bulletins_pdf.get_filigranne(
2022-03-05 12:47:08 +01:00
I["etud_etat"], prefs, decision_sem=I["decision_sem"]
2022-02-21 19:25:38 +01:00
)
2020-09-26 16:19:37 +02:00
I["demission"] = ""
2022-02-14 23:21:42 +01:00
if I["etud_etat"] == scu.DEMISSION:
2020-09-26 16:19:37 +02:00
I["demission"] = "(Démission)"
elif I["etud_etat"] == codes_cursus.DEF:
2020-09-26 16:19:37 +02:00
I["demission"] = "(Défaillant)"
# --- Notes
2021-12-24 00:08:25 +01:00
ues = nt.get_ues_stat_dict()
2021-12-26 19:15:47 +01:00
modimpls = nt.get_modimpls_dict()
2020-09-26 16:19:37 +02:00
moy_gen = nt.get_etud_moy_gen(etudid)
2021-12-24 00:08:25 +01:00
I["nb_inscrits"] = len(nt.etud_moy_gen_ranks)
2021-02-01 16:23:11 +01:00
I["moy_gen"] = scu.fmt_note(moy_gen)
I["moy_min"] = scu.fmt_note(nt.moy_min)
I["moy_max"] = scu.fmt_note(nt.moy_max)
2020-09-26 16:19:37 +02:00
I["mention"] = ""
if dpv:
decision_sem = dpv["decisions"][0]["decision_sem"]
if decision_sem and codes_cursus.code_semestre_validant(decision_sem["code"]):
2021-02-01 16:23:11 +01:00
I["mention"] = scu.get_mention(moy_gen)
2020-09-26 16:19:37 +02:00
if dpv and dpv["decisions"][0]:
I["sum_ects"] = dpv["decisions"][0]["sum_ects"]
I["sum_ects_capitalises"] = dpv["decisions"][0]["sum_ects_capitalises"]
else:
I["sum_ects"] = 0
I["sum_ects_capitalises"] = 0
2021-02-01 16:23:11 +01:00
I["moy_moy"] = scu.fmt_note(nt.moy_moy) # moyenne des moyennes generales
2021-07-11 18:18:44 +02:00
if (not isinstance(moy_gen, str)) and (not isinstance(nt.moy_moy, str)):
2020-09-26 16:19:37 +02:00
I["moy_gen_bargraph_html"] = "&nbsp;" + htmlutils.horizontal_bargraph(
moy_gen * 5, nt.moy_moy * 5
)
else:
I["moy_gen_bargraph_html"] = ""
if prefs["bul_show_rangs"]:
rang = str(nt.get_etud_rang(etudid))
else:
rang = ""
rang_gr, ninscrits_gr, gr_name = get_etud_rangs_groups(
etudid, partitions, partitions_etud_groups, nt
2020-09-26 16:19:37 +02:00
)
if nt.get_moduleimpls_attente():
# n'affiche pas le rang sur le bulletin s'il y a des
# notes en attente dans ce semestre
2021-02-01 16:23:11 +01:00
rang = scu.RANG_ATTENTE_STR
rang_gr = collections.defaultdict(lambda: scu.RANG_ATTENTE_STR)
2022-01-16 23:47:52 +01:00
inscriptions_counts = nt.get_inscriptions_counts()
2020-09-26 16:19:37 +02:00
I["rang"] = rang
I["rang_gr"] = rang_gr
I["gr_name"] = gr_name
I["ninscrits_gr"] = ninscrits_gr
2021-12-24 00:08:25 +01:00
I["nbetuds"] = len(nt.etud_moy_gen_ranks)
2022-01-16 23:47:52 +01:00
I["nb_demissions"] = inscriptions_counts[scu.DEMISSION]
I["nb_defaillants"] = inscriptions_counts[scu.DEF]
2020-09-26 16:19:37 +02:00
if prefs["bul_show_rangs"]:
I["rang_nt"] = "%s / %d" % (
rang,
2022-01-16 23:47:52 +01:00
inscriptions_counts[scu.INSCRIT],
2020-09-26 16:19:37 +02:00
)
I["rang_txt"] = "Rang " + I["rang_nt"]
else:
I["rang_nt"], I["rang_txt"] = "", ""
I["note_max"] = 20.0 # notes toujours sur 20
2022-01-25 10:45:13 +01:00
I["bonus_sport_culture"] = nt.bonus[etudid] if nt.bonus is not None else 0.0
2020-09-26 16:19:37 +02:00
# Liste les UE / modules /evals
I["ues"] = []
I["matieres_modules"] = {}
I["matieres_modules_capitalized"] = {}
for ue in ues:
u = ue.copy()
ue_status = nt.get_etud_ue_status(etudid, ue["ue_id"])
2022-01-25 10:45:13 +01:00
if (
ModuleImplInscription.nb_inscriptions_dans_ue(
formsemestre_id, etudid, ue["ue_id"]
)
== 0
) and not ue_status["is_capitalized"]:
# saute les UE où l'on est pas inscrit et n'avons pas de capitalisation
2022-01-25 10:45:13 +01:00
continue
2020-09-26 16:19:37 +02:00
u["ue_status"] = ue_status # { 'moy', 'coef_ue', ...}
if ue["type"] != codes_cursus.UE_SPORT:
2021-02-01 16:23:11 +01:00
u["cur_moy_ue_txt"] = scu.fmt_note(ue_status["cur_moy_ue"])
2020-09-26 16:19:37 +02:00
else:
2022-01-25 10:45:13 +01:00
if nt.bonus is not None:
x = scu.fmt_note(nt.bonus[etudid], keep_numeric=True)
else:
x = ""
2021-07-11 18:18:44 +02:00
if isinstance(x, str):
if nt.bonus_ues is None:
u["cur_moy_ue_txt"] = "pas de bonus"
else:
u["cur_moy_ue_txt"] = "bonus appliqué sur les UEs"
2020-09-26 16:19:37 +02:00
else:
2022-02-21 19:25:38 +01:00
u["cur_moy_ue_txt"] = f"bonus de {fmt_note(x)} points"
if nt.bonus_ues is not None:
u["cur_moy_ue_txt"] += " (+ues)"
2021-02-01 16:23:11 +01:00
u["moy_ue_txt"] = scu.fmt_note(ue_status["moy"])
if ue_status["coef_ue"] is not None:
2021-02-01 16:23:11 +01:00
u["coef_ue_txt"] = scu.fmt_coef(ue_status["coef_ue"])
2020-09-26 16:19:37 +02:00
else:
u["coef_ue_txt"] = "-"
2020-09-26 16:19:37 +02:00
if (
dpv
and dpv["decisions"][0]["decisions_ue"]
and ue["ue_id"] in dpv["decisions"][0]["decisions_ue"]
):
u["ects"] = dpv["decisions"][0]["decisions_ue"][ue["ue_id"]]["ects"]
if ue["type"] == codes_cursus.UE_ELECTIVE:
2020-09-26 16:19:37 +02:00
u["ects"] = (
"%g+" % u["ects"]
) # ajoute un "+" pour indiquer ECTS d'une UE élective
else:
if ue_status["is_capitalized"]:
u["ects"] = ue_status["ue"].get("ects", "-")
else:
u["ects"] = "-"
modules, ue_attente = _ue_mod_bulletin(
etudid, formsemestre_id, ue["ue_id"], modimpls, nt, version
2020-09-26 16:19:37 +02:00
)
#
u["modules"] = modules # detail des modules de l'UE (dans le semestre courant)
# auparavant on filtrait les modules sans notes
# si ue_status['cur_moy_ue'] != 'NA' alors u['modules'] = [] (pas de moyenne => pas de modules)
2024-02-22 17:39:18 +01:00
u["modules_capitalized"] = (
[]
) # modules de l'UE capitalisée (liste vide si pas capitalisée)
2022-07-11 18:33:48 +02:00
if ue_status["is_capitalized"] and ue_status["formsemestre_id"] is not None:
sem_origin = db.session.get(FormSemestre, ue_status["formsemestre_id"])
2024-02-22 17:39:18 +01:00
u["ue_descr_txt"] = (
f'capitalisée le {ndb.DateISOtoDMY(ue_status["event_date"])}'
)
2022-07-11 18:33:48 +02:00
u["ue_descr_html"] = (
f"""<a href="{ url_for( 'notes.formsemestre_bulletinetud',
scodoc_dept=g.scodoc_dept, formsemestre_id=sem_origin.id, etudid=etudid)}"
title="{sem_origin.titre_annee()}" class="bull_link"
>{u["ue_descr_txt"]}</a>
2022-02-10 11:54:03 +01:00
"""
2022-07-11 18:33:48 +02:00
if sem_origin
else ""
)
if ue_status["moy"] != "NA":
# détail des modules de l'UE capitalisée
formsemestre_cap = db.session.get(
FormSemestre, ue_status["formsemestre_id"]
)
2022-02-09 23:22:00 +01:00
nt_cap: NotesTableCompat = res_sem.load_formsemestre_results(
formsemestre_cap
)
2020-09-26 16:19:37 +02:00
u["modules_capitalized"], _ = _ue_mod_bulletin(
2020-09-26 16:19:37 +02:00
etudid,
formsemestre_id,
ue_status["capitalized_ue_id"],
2021-12-26 19:15:47 +01:00
nt_cap.get_modimpls_dict(),
2020-09-26 16:19:37 +02:00
nt_cap,
version,
)
I["matieres_modules_capitalized"].update(
_sort_mod_by_matiere(u["modules_capitalized"], nt_cap, etudid)
)
else:
if prefs["bul_show_ue_rangs"] and ue["type"] != codes_cursus.UE_SPORT:
if ue_attente or nt.ue_rangs[ue["ue_id"]][0] is None:
2020-09-26 16:19:37 +02:00
u["ue_descr_txt"] = "%s/%s" % (
2021-02-01 16:23:11 +01:00
scu.RANG_ATTENTE_STR,
2020-09-26 16:19:37 +02:00
nt.ue_rangs[ue["ue_id"]][1],
)
else:
u["ue_descr_txt"] = "%s/%s" % (
nt.ue_rangs[ue["ue_id"]][0][etudid],
nt.ue_rangs[ue["ue_id"]][1],
)
u["ue_descr_html"] = u["ue_descr_txt"]
else:
u["ue_descr_txt"] = u["ue_descr_html"] = ""
if ue_status["is_capitalized"] or modules:
I["ues"].append(u) # ne montre pas les UE si non inscrit
# Accès par matieres
# En #sco92, pas d'information
I["matieres_modules"].update(_sort_mod_by_matiere(modules, nt, etudid))
2020-09-26 16:19:37 +02:00
#
2022-03-05 12:47:08 +01:00
C = make_context_dict(formsemestre, I["etud"])
2020-09-26 16:19:37 +02:00
C.update(I)
#
# log( 'C = \n%s\n' % pprint.pformat(C) ) # tres pratique pour voir toutes les infos dispo
return C
2022-01-16 23:47:52 +01:00
def _get_etud_etat_html(etat: str) -> str:
"""chaine html représentant l'état (backward compat sco7)"""
if etat == scu.INSCRIT:
2022-01-16 23:47:52 +01:00
return ""
elif etat == scu.DEMISSION:
2022-01-16 23:47:52 +01:00
return ' <font color="red">(DEMISSIONNAIRE)</font> '
elif etat == scu.DEF:
2022-01-16 23:47:52 +01:00
return ' <font color="red">(DEFAILLANT)</font> '
else:
return f' <font color="red">({etat})</font> '
2022-01-16 23:47:52 +01:00
2020-09-26 16:19:37 +02:00
def _sort_mod_by_matiere(modlist, nt, etudid):
matmod = {} # { matiere_id : [] }
for mod in modlist:
matiere_id = mod["module"]["matiere_id"]
if matiere_id not in matmod:
moy = nt.get_etud_mat_moy(matiere_id, etudid)
matmod[matiere_id] = {
"titre": mod["mat"]["titre"],
"modules": mod,
"moy": moy,
2021-02-01 16:23:11 +01:00
"moy_txt": scu.fmt_note(moy),
2020-09-26 16:19:37 +02:00
}
return matmod
def _ue_mod_bulletin(
etudid, formsemestre_id, ue_id, modimpls, nt: NotesTableCompat, version
):
2020-09-26 16:19:37 +02:00
"""Infos sur les modules (et évaluations) dans une UE
(ajoute les informations aux modimpls)
Result: liste de modules de l'UE avec les infos dans chacun (seulement
ceux l'étudiant est inscrit).
2020-09-26 16:19:37 +02:00
"""
2021-06-13 23:37:14 +02:00
bul_show_mod_rangs = sco_preferences.get_preference(
"bul_show_mod_rangs", formsemestre_id
2021-06-13 23:37:14 +02:00
)
bul_show_abs_modules = sco_preferences.get_preference(
"bul_show_abs_modules", formsemestre_id
2020-09-26 16:19:37 +02:00
)
if bul_show_abs_modules:
2021-08-19 10:28:35 +02:00
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
2021-02-01 16:23:11 +01:00
debut_sem = ndb.DateDMYtoISO(sem["date_debut"])
fin_sem = ndb.DateDMYtoISO(sem["date_fin"])
2020-09-26 16:19:37 +02:00
ue_modimpls = [mod for mod in modimpls if mod["module"]["ue_id"] == ue_id]
mods = [] # result
ue_attente = False # true si une eval en attente dans cette UE
for modimpl in ue_modimpls:
2023-08-25 17:58:57 +02:00
modimpl_results = nt.modimpls_results.get(modimpl["moduleimpl_id"])
2020-09-26 16:19:37 +02:00
mod_attente = False
mod = modimpl.copy()
mod_moy = nt.get_etud_mod_moy(
modimpl["moduleimpl_id"], etudid
) # peut etre 'NI'
is_malus = mod["module"]["module_type"] == ModuleType.MALUS
2020-09-26 16:19:37 +02:00
if bul_show_abs_modules:
2024-03-01 12:40:05 +01:00
_, nbabsjust, nbabs = sco_assiduites.get_assiduites_count(etudid, sem)
2021-07-19 19:53:01 +02:00
mod_abs = [nbabs, nbabsjust]
2021-02-01 16:23:11 +01:00
mod["mod_abs_txt"] = scu.fmt_abs(mod_abs)
2020-09-26 16:19:37 +02:00
else:
mod["mod_abs_txt"] = ""
2021-02-01 16:23:11 +01:00
mod["mod_moy_txt"] = scu.fmt_note(mod_moy)
2020-09-26 16:19:37 +02:00
if mod["mod_moy_txt"][:2] == "NA":
mod["mod_moy_txt"] = "-"
if is_malus:
2021-09-13 17:10:38 +02:00
if isinstance(mod_moy, str):
mod["mod_moy_txt"] = "-"
mod["mod_coef_txt"] = "-"
elif mod_moy > 0:
2021-02-01 16:23:11 +01:00
mod["mod_moy_txt"] = scu.fmt_note(mod_moy)
mod["mod_coef_txt"] = "Malus"
elif mod_moy < 0:
2021-02-01 16:23:11 +01:00
mod["mod_moy_txt"] = scu.fmt_note(-mod_moy)
mod["mod_coef_txt"] = "Bonus"
else:
mod["mod_moy_txt"] = "-"
mod["mod_coef_txt"] = "-"
2020-09-26 16:19:37 +02:00
else:
2021-02-01 16:23:11 +01:00
mod["mod_coef_txt"] = scu.fmt_coef(modimpl["module"]["coefficient"])
2020-09-26 16:19:37 +02:00
if mod["mod_moy_txt"] != "NI": # ne montre pas les modules 'non inscrit'
mods.append(mod)
if is_malus: # n'affiche pas les statistiques sur les modules malus
mod["stats"] = {
"moy": "",
"max": "",
"min": "",
"nb_notes": "",
"nb_missing": "",
"nb_valid_evals": "",
}
else:
mod["stats"] = nt.get_mod_stats(modimpl["moduleimpl_id"])
mod["mod_descr_txt"] = "Module %s, coef. %s (%s)" % (
modimpl["module"]["titre"],
2021-02-01 16:23:11 +01:00
scu.fmt_coef(modimpl["module"]["coefficient"]),
sco_users.user_info(modimpl["responsable_id"])["nomcomplet"],
2020-09-26 16:19:37 +02:00
)
2023-08-25 17:58:57 +02:00
link_mod = f"""<a class="bull_link" href="{
url_for("notes.moduleimpl_status",
scodoc_dept=g.scodoc_dept,
2023-08-25 17:58:57 +02:00
moduleimpl_id=modimpl["moduleimpl_id"]
)
}" title="{mod["mod_descr_txt"]}">"""
if sco_preferences.get_preference("bul_show_codemodules", formsemestre_id):
2020-09-26 16:19:37 +02:00
mod["code"] = modimpl["module"]["code"]
mod["code_html"] = link_mod + (mod["code"] or "") + "</a>"
2020-09-26 16:19:37 +02:00
else:
mod["code"] = mod["code_html"] = ""
mod["name"] = (
modimpl["module"]["abbrev"] or modimpl["module"]["titre"] or ""
)
mod["name_html"] = link_mod + mod["name"] + "</a>"
mod_descr = "Module %s, coef. %s (%s)" % (
modimpl["module"]["titre"],
2021-02-01 16:23:11 +01:00
scu.fmt_coef(modimpl["module"]["coefficient"]),
sco_users.user_info(modimpl["responsable_id"])["nomcomplet"],
2020-09-26 16:19:37 +02:00
)
link_mod = (
'<a class="bull_link" href="moduleimpl_status?moduleimpl_id=%s" title="%s">'
% (modimpl["moduleimpl_id"], mod_descr)
)
if sco_preferences.get_preference("bul_show_codemodules", formsemestre_id):
mod["code_txt"] = modimpl["module"]["code"] or ""
2020-09-26 16:19:37 +02:00
mod["code_html"] = link_mod + mod["code_txt"] + "</a>"
else:
mod["code_txt"] = ""
mod["code_html"] = ""
# Evaluations: notes de chaque eval
2023-08-25 17:58:57 +02:00
evaluations_completes = nt.get_modimpl_evaluations_completes(
modimpl["moduleimpl_id"]
)
# On liste séparément les éval. complètes ou non
2020-09-26 16:19:37 +02:00
mod["evaluations"] = []
2023-08-25 17:58:57 +02:00
mod["evaluations_incompletes"] = []
complete_eval_ids = {e.id for e in evaluations_completes}
all_evals: list[Evaluation] = Evaluation.query.filter_by(
moduleimpl_id=modimpl["moduleimpl_id"]
).order_by(Evaluation.numero, Evaluation.date_debut)
# (plus ancienne d'abord)
for e in all_evals:
if e.is_blocked():
continue # ignore évaluations bloquées
2023-08-25 17:58:57 +02:00
if not e.visibulletin and version != "long":
continue
is_complete = e.id in complete_eval_ids
e_dict = e.to_dict_bul()
# Note à l'évaluation:
val = modimpl_results.evals_notes[e.id].get(etudid, "NP")
# Affiche "bonus" quand les points de malus sont négatifs
if is_malus:
if val == "NP":
e_dict["name"] = "Points de bonus/malus sur cette UE"
elif val > 0:
e_dict["name"] = "Points de malus sur cette UE"
2020-09-26 16:19:37 +02:00
else:
2023-08-25 17:58:57 +02:00
e_dict["name"] = "Points de bonus sur cette UE"
else:
e_dict[
"name"
] = f"""{e.description or ""} {
e.descr_date()
if e.date_debut and not is_complete
2023-08-25 17:58:57 +02:00
else ""}"""
e_dict["target_html"] = url_for(
"notes.evaluation_listenotes",
scodoc_dept=g.scodoc_dept,
2023-08-25 17:58:57 +02:00
evaluation_id=e.id,
fmt="html",
tf_submitted=1,
2020-09-26 16:19:37 +02:00
)
2023-08-25 17:58:57 +02:00
e_dict[
"name_html"
] = f"""<a class="bull_link" href="{
2023-08-25 17:58:57 +02:00
e_dict['target_html']}">{e_dict['name']}</a>"""
if is_complete: # évaluation complète
# val est NP si etud demissionnaire
if val == "NP":
e_dict["note_txt"] = "nd"
e_dict["note_html"] = '<span class="note_nd">nd</span>'
e_dict["coef_txt"] = scu.fmt_coef(e["coefficient"])
2020-09-26 16:19:37 +02:00
else:
2023-08-25 17:58:57 +02:00
# (-0.15) s'affiche "bonus de 0.15"
if is_malus:
val = abs(val)
e_dict["note_txt"] = e_dict["note_html"] = scu.fmt_note(
val, note_max=e.note_max
)
else: # évaluation incomplète: pas de note
e_dict["note_txt"] = e_dict["note_html"] = ""
if is_malus:
e_dict["coef_txt"] = ""
else:
e_dict["coef_txt"] = scu.fmt_coef(e.coefficient)
if e.evaluation_type == Evaluation.EVALUATION_RATTRAPAGE:
2023-08-25 17:58:57 +02:00
e_dict["coef_txt"] = "rat."
elif e.evaluation_type == Evaluation.EVALUATION_SESSION2:
2023-08-25 17:58:57 +02:00
e_dict["coef_txt"] = "Ses. 2"
if modimpl_results.evaluations_etat[e.id].nb_attente:
2020-09-26 16:19:37 +02:00
mod_attente = True # une eval en attente dans ce module
2023-08-25 17:58:57 +02:00
if ((not is_malus) or (val != "NP")) and (
(
e.evaluation_type == Evaluation.EVALUATION_NORMALE
or not np.isnan(val)
)
):
# ne liste pas les eval malus sans notes
# ni les rattrapages, sessions 2 et bonus si pas de note
2023-08-25 17:58:57 +02:00
if e.id in complete_eval_ids:
mod["evaluations"].append(e_dict)
else:
mod["evaluations_incompletes"].append(e_dict)
2020-09-26 16:19:37 +02:00
# Classement
2022-02-11 16:34:02 +01:00
if (
bul_show_mod_rangs
and (nt.mod_rangs is not None)
and mod["mod_moy_txt"] != "-"
and not is_malus
):
2020-09-26 16:19:37 +02:00
rg = nt.mod_rangs[modimpl["moduleimpl_id"]]
if rg[0] is None:
mod["mod_rang_txt"] = ""
2020-09-26 16:19:37 +02:00
else:
if mod_attente: # nt.get_moduleimpls_attente():
mod["mod_rang"] = scu.RANG_ATTENTE_STR
else:
mod["mod_rang"] = rg[0][etudid]
mod["mod_eff"] = rg[1] # effectif dans ce module
mod["mod_rang_txt"] = "%s/%s" % (mod["mod_rang"], mod["mod_eff"])
2020-09-26 16:19:37 +02:00
else:
mod["mod_rang_txt"] = ""
if mod_attente:
ue_attente = True
return mods, ue_attente
def get_etud_rangs_groups(
etudid: int, partitions, partitions_etud_groups, nt: NotesTableCompat
2020-09-26 16:19:37 +02:00
):
"""Ramene rang et nb inscrits dans chaque partition"""
2020-09-26 16:19:37 +02:00
rang_gr, ninscrits_gr, gr_name = {}, {}, {}
for partition in partitions:
if partition["partition_name"] != None:
partition_id = partition["partition_id"]
if etudid in partitions_etud_groups[partition_id]:
group = partitions_etud_groups[partition_id][etudid]
(
rang_gr[partition_id],
ninscrits_gr[partition_id],
) = nt.get_etud_rang_group(etudid, group["group_id"])
gr_name[partition_id] = group["group_name"]
else: # etudiant non present dans cette partition
rang_gr[partition_id], ninscrits_gr[partition_id] = "", ""
gr_name[partition_id] = ""
return rang_gr, ninscrits_gr, gr_name
def etud_descr_situation_semestre(
etudid,
formsemestre: FormSemestre,
2020-09-26 16:19:37 +02:00
ne="",
fmt="html", # currently unused
2020-09-26 16:19:37 +02:00
show_decisions=True,
show_uevalid=True,
show_date_inscr=True,
show_mention=False,
):
"""Dict décrivant la situation de l'étudiant dans ce semestre.
Si fmt == 'html', peut inclure du balisage html (actuellement inutilisé)
2020-09-26 16:19:37 +02:00
situation : chaine résumant en français la situation de l'étudiant.
Par ex. "Inscrit le 31/12/1999. Décision jury: Validé. ..."
2020-09-26 16:19:37 +02:00
date_inscription : (vide si show_date_inscr est faux)
date_demission : (vide si pas demission ou si show_date_inscr est faux)
descr_inscription : "Inscrit" ou "Pas inscrit[e]"
descr_demission : "Démission le 01/02/2000" ou vide si pas de démission
descr_defaillance : "Défaillant" ou vide si non défaillant.
decision_jury : "Validé", "Ajourné", ... (code semestre)
descr_decision_jury : "Décision jury: Validé" (une phrase)
2022-02-14 23:21:42 +01:00
decision_sem :
2020-09-26 16:19:37 +02:00
decisions_ue : noms (acronymes) des UE validées, séparées par des virgules.
descr_decisions_ue : ' UE acquises: UE1, UE2', ou vide si pas de dec. ou si pas show_uevalid
descr_mention : 'Mention Bien', ou vide si pas de mention ou si pas show_mention
diplomation : "Diplôme obtenu." ou ""
parcours_titre, parcours_code, refcomp_specialite, refcomp_specialite_long
diplome_dut120_descr: phrase explicative si DUT enregistré (en BUT)
diplome_dut120: booléen, vrai si DUT enregistré (en BUT)
2020-09-26 16:19:37 +02:00
"""
# Fonction utilisée par tous les bulletins (APC ou classiques)
infos = collections.defaultdict(str)
2020-09-26 16:19:37 +02:00
# --- Situation et décisions jury
date_inscr, date_dem, date_def = _dates_insc_dem_def(etudid, formsemestre.id)
2020-09-26 16:19:37 +02:00
if show_date_inscr:
if not date_inscr:
infos["date_inscription"] = ""
infos["descr_inscription"] = f"Pas inscrit{ne}"
2020-09-26 16:19:37 +02:00
else:
infos["date_inscription"] = date_inscr
infos["descr_inscription"] = f"Inscrit{ne} le {date_inscr}"
2020-09-26 16:19:37 +02:00
else:
infos["date_inscription"] = ""
infos["descr_inscription"] = ""
infos["descr_defaillance"] = ""
# Parcours BUT
infos["parcours_titre"] = ""
infos["parcours_code"] = ""
infos["refcomp_specialite"] = ""
infos["refcomp_specialite_long"] = ""
if formsemestre.formation.is_apc():
res: ResultatsSemestreBUT = res_sem.load_formsemestre_results(formsemestre)
2024-02-22 17:39:18 +01:00
try:
parcour_id = res.etuds_parcour_id[etudid]
except KeyError as exc:
log("sco_bulletins: ScoTemporaryError 240222")
raise ScoTemporaryError() from exc
parcour: ApcParcours = (
db.session.get(ApcParcours, parcour_id) if parcour_id is not None else None
)
if parcour:
infos["parcours_titre"] = parcour.libelle or ""
infos["parcours_code"] = parcour.code or ""
refcomp = parcour.referentiel
if refcomp:
infos["refcomp_specialite"] = refcomp.specialite
infos["refcomp_specialite_long"] = refcomp.specialite_long
2022-07-11 18:33:48 +02:00
# Décision: valeurs par defaut vides:
infos["decision_jury"] = infos["descr_decision_jury"] = ""
infos["decision_sem"] = ""
infos["decisions_ue"] = infos["descr_decisions_ue"] = ""
infos["descr_decisions_niveaux"] = infos["descr_decisions_rcue"] = ""
infos["descr_decision_annee"] = ""
2020-09-26 16:19:37 +02:00
infos["descr_demission"] = f"Démission le {date_dem}." if date_dem else ""
infos["date_demission"] = date_dem if date_dem else ""
2020-09-26 16:19:37 +02:00
if date_dem:
2022-07-11 18:33:48 +02:00
infos["decision_jury"] = infos["descr_decision_jury"] = "Démission"
infos["situation"] = ". ".join(
[x for x in [infos["descr_inscription"], infos["descr_demission"]] if x]
)
2020-09-26 16:19:37 +02:00
return infos, None # ne donne pas les dec. de jury pour les demissionnaires
infos["descr_defaillance"] = f"Défaillant{ne}" if date_def else ""
infos["date_defaillance"] = date_def or ""
2020-09-26 16:19:37 +02:00
if date_def:
infos["descr_decision_jury"] = f"Défaillant{ne}"
2020-09-26 16:19:37 +02:00
dpv = sco_pv_dict.dict_pvjury(formsemestre.id, etudids=[etudid])
2022-02-14 23:21:42 +01:00
if dpv:
infos["decision_sem"] = dpv["decisions"][0]["decision_sem"]
2020-09-26 16:19:37 +02:00
if not show_decisions:
return infos, dpv
2022-02-14 23:21:42 +01:00
# Décisions de jury:
2020-09-26 16:19:37 +02:00
pv = dpv["decisions"][0]
descr_dec = ""
2020-09-26 16:19:37 +02:00
if pv["decision_sem_descr"]:
infos["decision_jury"] = pv["decision_sem_descr"]
infos["descr_decision_jury"] = "Décision jury: " + pv["decision_sem_descr"]
descr_dec = infos["descr_decision_jury"]
2020-09-26 16:19:37 +02:00
else:
infos["descr_decision_jury"] = ""
infos["decision_jury"] = ""
2020-09-26 16:19:37 +02:00
if pv["decisions_ue_descr"] and show_uevalid:
infos["decisions_ue"] = pv["decisions_ue_descr"]
infos["descr_decisions_ue"] = " UE acquises: " + pv["decisions_ue_descr"]
2020-09-26 16:19:37 +02:00
else:
2022-07-11 18:33:48 +02:00
infos["decisions_ue"] = ""
2020-09-26 16:19:37 +02:00
infos["descr_decisions_ue"] = ""
infos["mention"] = pv["mention"]
if pv["mention"] and show_mention:
descr_mention = f"Mention {pv['mention']}"
else:
descr_mention = ""
# Décisions APC / BUT
if pv.get("decision_annee", {}) and sco_preferences.get_preference(
"bul_but_code_annuel", formsemestre.id
):
infos["descr_decision_annee"] = "Décision année: " + pv.get(
"decision_annee", {}
).get("code", "")
else:
infos["descr_decision_annee"] = ""
2022-07-08 23:58:27 +02:00
infos["descr_decisions_rcue"] = pv.get("descr_decisions_rcue", "")
infos["descr_decisions_niveaux"] = pv.get("descr_decisions_niveaux", "")
2020-09-26 16:19:37 +02:00
descr_autorisations = ""
2020-09-26 16:19:37 +02:00
if not pv["validation_parcours"]: # parcours non terminé
if pv["autorisations_descr"]:
descr_autorisations = (
f"Autorisé à s'inscrire en {pv['autorisations_descr']}."
)
2020-09-26 16:19:37 +02:00
else:
descr_dec += " Diplôme obtenu."
infos["diplomation"] = "Diplôme obtenu." if pv["validation_parcours"] else ""
# Ajoute diplome_dut120_descr et diplome_dut120
sco_pv_lettres_inviduelles.add_dut120_infos(formsemestre, etudid, infos)
_format_situation_fields(
infos,
[
"descr_inscription",
"descr_defaillance",
"descr_decisions_ue",
"diplome_dut120_descr",
"descr_decision_annee",
],
[descr_dec, descr_mention, descr_autorisations],
)
2020-09-26 16:19:37 +02:00
return infos, dpv
def _dates_insc_dem_def(etudid, formsemestre_id) -> tuple:
"Cherche les dates d'inscription, démission et défaillance de l'étudiant"
cnx = ndb.GetDBConnexion()
events = sco_etud.scolar_events_list(
cnx, args={"etudid": etudid, "formsemestre_id": formsemestre_id}
)
date_inscr = None
date_dem = None
date_def = None
for event in events:
event_type = event["event_type"]
if event_type == "INSCRIPTION":
if date_inscr:
# plusieurs inscriptions ???
# date_inscr += ', ' + event['event_date'] + ' (!)'
# il y a eu une erreur qui a laissé un event 'inscription'
# on l'efface:
log(
f"etud_descr_situation_semestre: removing duplicate INSCRIPTION event for etudid={etudid} !"
)
sco_etud.scolar_events_delete(cnx, event["event_id"])
else:
date_inscr = event["event_date"]
elif event_type == "DEMISSION":
# assert date_dem == None, 'plusieurs démissions !'
if date_dem: # cela ne peut pas arriver sauf bug (signale a Evry 2013?)
log(
f"etud_descr_situation_semestre: removing duplicate DEMISSION event for etudid={etudid} !"
)
sco_etud.scolar_events_delete(cnx, event["event_id"])
else:
date_dem = event["event_date"]
elif event_type == "DEFAILLANCE":
if date_def:
log(
f"etud_descr_situation_semestre: removing duplicate DEFAILLANCE event for etudid={etudid} !"
)
sco_etud.scolar_events_delete(cnx, event["event_id"])
else:
date_def = event["event_date"]
return date_inscr, date_dem, date_def
def _format_situation_fields(
infos, field_names: list[str], extra_values: list[str]
) -> None:
"""Réuni les champs pour former le paragraphe "situation", et ajoute la pontuation
aux champs.
"""
infos["situation"] = ". ".join(
x
for x in [infos.get(field_name, "") for field_name in field_names]
+ [field for field in extra_values if field]
if x
)
for field_name in field_names:
field = infos.get(field_name, "")
if field and not field.endswith("."):
infos[field_name] += "."
2020-09-26 16:19:37 +02:00
# ------ Page bulletin
def formsemestre_bulletinetud(
etud: Identite = None,
2020-09-26 16:19:37 +02:00
formsemestre_id=None,
fmt=None,
2020-09-26 16:19:37 +02:00
version="long",
xml_with_decisions=False,
force_publishing=False, # force publication meme si semestre non publie sur "portail"
prefer_mail_perso=False,
):
2022-07-11 18:33:48 +02:00
"""Page bulletin de notes pour
- HTML des formations classiques (non BUT)
- le format "oldjson" (les "json" sont générés à part, voir get_formsemestre_bulletin_etud_json)
- les formats PDF, XML et mail pdf (toutes formations)
Note: le format XML n'est plus maintenu et pour les BUT ne contient pas
toutes les informations. Privilégier le format JSON.
Paramètres:
- version: pour les formations classqiues, versions short/selectedevals/long
- xml_with_decisions: inclue ou non les
- force_publishing: renvoie le bulletin même si semestre non publie sur "portail"
- prefer_mail_perso: pour pdfmail, utilise adresse mail perso en priorité.
"""
fmt = fmt or "html"
formsemestre: FormSemestre = db.session.get(FormSemestre, formsemestre_id)
2022-02-14 23:21:42 +01:00
if not formsemestre:
raise ScoValueError(f"semestre {formsemestre_id} inconnu !")
2020-09-26 16:19:37 +02:00
2021-07-13 17:00:25 +02:00
bulletin = do_formsemestre_bulletinetud(
2022-02-14 23:21:42 +01:00
formsemestre,
etud,
fmt=fmt,
2021-07-13 17:00:25 +02:00
version=version,
xml_with_decisions=xml_with_decisions,
force_publishing=force_publishing,
prefer_mail_perso=prefer_mail_perso,
)[0]
if fmt not in {"html", "pdfmail"}:
2023-07-12 14:06:34 +02:00
filename = scu.bul_filename(formsemestre, etud)
mime, suffix = scu.get_mime_suffix(fmt)
return scu.send_file(bulletin, filename, mime=mime, suffix=suffix)
elif fmt == "pdfmail":
2022-03-22 19:18:25 +01:00
return ""
2021-07-13 17:00:25 +02:00
H = [
_formsemestre_bulletinetud_header_html(etud, formsemestre, fmt, version),
2021-07-13 17:00:25 +02:00
bulletin,
2022-03-07 23:43:48 +01:00
render_template(
"bul_foot.j2",
2022-03-16 00:13:58 +01:00
appreciations=None, # déjà affichées
css_class="bul_classic_foot",
2022-03-07 23:43:48 +01:00
etud=etud,
formsemestre=formsemestre,
inscription_courante=etud.inscription_courante(),
inscription_str=etud.inscription_descr()["inscription_str"],
),
html_sco_header.sco_footer(),
2021-07-13 17:00:25 +02:00
]
return "".join(H)
2020-09-26 16:19:37 +02:00
2021-08-20 10:51:42 +02:00
def can_send_bulletin_by_mail(formsemestre_id):
"""True if current user is allowed to send a bulletin (pdf) by mail"""
2021-08-19 10:28:35 +02:00
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
2020-09-26 16:19:37 +02:00
return (
sco_preferences.get_preference("bul_mail_allowed_for_all", formsemestre_id)
or current_user.has_permission(Permission.EditFormSemestre)
2021-08-22 13:24:36 +02:00
or current_user.id in sem["responsables"]
) and not ScoDocSiteConfig.is_bul_pdf_disabled()
2020-09-26 16:19:37 +02:00
def do_formsemestre_bulletinetud(
2022-02-14 23:21:42 +01:00
formsemestre: FormSemestre,
etud: Identite,
2020-09-26 16:19:37 +02:00
version="long", # short, long, selectedevals
fmt=None,
2023-02-15 16:15:53 +01:00
xml_with_decisions: bool = False,
force_publishing: bool = False,
prefer_mail_perso: bool = False,
with_img_signatures_pdf: bool = True,
2020-09-26 16:19:37 +02:00
):
"""Génère le bulletin au format demandé.
2022-07-11 18:33:48 +02:00
Utilisé pour:
- HTML des formations classiques (non BUT)
- le format "oldjson" (les json sont générés à part, voir get_formsemestre_bulletin_etud_json)
- les formats PDF, XML et mail pdf (toutes formations)
2023-02-15 16:15:53 +01:00
Options:
- xml_with_decisions: force décisions dans XML
- force_publishing: force publication meme si semestre non publié sur "portail"
- prefer_mail_perso: mails envoyés sur adresse perso si non vide
- with_img_signatures_pdf: si faux, ne met pas les signatures dans le footer PDF.
2022-07-11 18:33:48 +02:00
Résultat: (bul, filigranne)
bul est str ou bytes au format demandé (html, pdf, pdfmail, pdfpart, xml, json)
2020-09-26 16:19:37 +02:00
et filigranne est un message à placer en "filigranne" (eg "Provisoire").
"""
from app.but import bulletin_but_court
fmt = fmt or "html"
if fmt == "xml":
2021-07-10 16:01:37 +02:00
bul = sco_bulletins_xml.make_xml_formsemestre_bulletinetud(
2022-02-14 23:21:42 +01:00
formsemestre.id,
etud.id,
2021-07-10 16:01:37 +02:00
xml_with_decisions=xml_with_decisions,
force_publishing=force_publishing,
version=version,
2020-09-26 16:19:37 +02:00
)
2021-07-10 16:01:37 +02:00
2020-09-26 16:19:37 +02:00
return bul, ""
elif fmt == "json": # utilisé pour classic et "oldjson"
2020-09-26 16:19:37 +02:00
bul = sco_bulletins_json.make_json_formsemestre_bulletinetud(
2022-02-14 23:21:42 +01:00
formsemestre.id,
etud.id,
2020-09-26 16:19:37 +02:00
xml_with_decisions=xml_with_decisions,
force_publishing=force_publishing,
version=version,
)
return bul, ""
if version.endswith("_mat"):
version = version[:-4] # enlève le "_mat"
2020-09-26 16:19:37 +02:00
2022-02-14 23:21:42 +01:00
if formsemestre.formation.is_apc():
bulletins_sem = bulletin_but.BulletinBUT(formsemestre)
bul_dict = bulletins_sem.bulletin_etud_complet(etud, version=version)
2022-02-14 23:21:42 +01:00
else:
bul_dict = formsemestre_bulletinetud_dict(formsemestre.id, etud.id)
2020-09-26 16:19:37 +02:00
if fmt == "html":
htm, _ = sco_bulletins_generator.make_formsemestre_bulletin_etud(
bul_dict, etud=etud, formsemestre=formsemestre, version=version, fmt="html"
2020-09-26 16:19:37 +02:00
)
return htm, bul_dict["filigranne"]
2020-09-26 16:19:37 +02:00
if fmt == "pdf" or fmt == "pdfpart":
bul, filename = sco_bulletins_generator.make_formsemestre_bulletin_etud(
bul_dict,
etud=etud,
formsemestre=formsemestre,
2020-09-26 16:19:37 +02:00
version=version,
fmt="pdf",
stand_alone=(fmt != "pdfpart"),
2023-02-15 16:15:53 +01:00
with_img_signatures_pdf=with_img_signatures_pdf,
2020-09-26 16:19:37 +02:00
)
if fmt == "pdf":
2020-09-26 16:19:37 +02:00
return (
scu.sendPDFFile(bul, filename),
bul_dict["filigranne"],
2020-09-26 16:19:37 +02:00
) # unused ret. value
else:
return bul, bul_dict["filigranne"]
2020-09-26 16:19:37 +02:00
elif fmt == "pdfmail":
2020-09-26 16:19:37 +02:00
# format pdfmail: envoie le pdf par mail a l'etud, et affiche le html
# check permission
2022-02-14 23:21:42 +01:00
if not can_send_bulletin_by_mail(formsemestre.id):
2020-09-26 16:19:37 +02:00
raise AccessDenied("Vous n'avez pas le droit d'effectuer cette opération !")
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",
)
2020-09-26 16:19:37 +02:00
if prefer_mail_perso:
recipient_addr = (
etud.get_first_email("emailperso") or etud.get_first_email()
)
2020-09-26 16:19:37 +02:00
else:
recipient_addr = etud.get_first_email() or etud.get_first_email(
"emailperso"
)
2020-09-26 16:19:37 +02:00
if not recipient_addr:
flash(f"{etud.nomprenom} n'a pas d'adresse e-mail !")
return False, bul_dict["filigranne"]
2022-03-22 19:18:25 +01:00
else:
mail_bulletin(formsemestre.id, bul_dict, pdfdata, filename, recipient_addr)
2022-03-22 19:18:25 +01:00
flash(f"mail envoyé à {recipient_addr}")
2020-09-26 16:19:37 +02:00
return True, bul_dict["filigranne"]
2022-03-22 19:18:25 +01:00
raise ValueError(f"do_formsemestre_bulletinetud: invalid format ({fmt})")
2020-09-26 16:19:37 +02:00
2022-07-11 18:33:48 +02:00
def mail_bulletin(formsemestre_id, infos, pdfdata, filename, recipient_addr):
2020-09-26 16:19:37 +02:00
"""Send bulletin by email to etud
If bul_mail_list_abs pref is true, put list of absences in mail body (text).
"""
2022-07-11 18:33:48 +02:00
etud = infos["etud"]
webmaster = sco_preferences.get_preference("bul_mail_contact_addr", formsemestre_id)
2021-06-13 23:37:14 +02:00
dept = scu.unescape_html(
sco_preferences.get_preference("DeptName", formsemestre_id)
2021-06-13 23:37:14 +02:00
)
copy_addr = sco_preferences.get_preference("email_copy_bulletins", formsemestre_id)
intro_mail = sco_preferences.get_preference("bul_intro_mail", formsemestre_id)
2020-09-26 16:19:37 +02:00
if intro_mail:
try:
hea = intro_mail % {
"nomprenom": etud["nomprenom"],
"dept": dept,
"webmaster": webmaster,
}
except KeyError as e:
raise ScoValueError(
"format 'Message d'accompagnement' (bul_intro_mail) invalide, revoir les réglages dans les préférences"
2022-03-10 09:28:59 +01:00
) from e
2020-09-26 16:19:37 +02:00
else:
hea = ""
if sco_preferences.get_preference("bul_mail_list_abs"):
from app.views.assiduites import generate_bul_list
etud_identite: Identite = Identite.get_etud(etud["etudid"])
form_semestre: FormSemestre = FormSemestre.get_formsemestre(formsemestre_id)
hea += "\n\n"
hea += generate_bul_list(etud_identite, form_semestre)
2020-09-26 16:19:37 +02:00
2022-07-11 18:33:48 +02:00
subject = f"""Relevé de notes de {etud["nomprenom"]}"""
2020-09-26 16:19:37 +02:00
recipients = [recipient_addr]
sender = email.get_from_addr()
2020-09-26 16:19:37 +02:00
if copy_addr:
bcc = copy_addr.strip().split(",")
2021-08-26 23:43:54 +02:00
else:
bcc = ""
2020-09-26 16:19:37 +02:00
# Attach pdf
2022-07-11 18:33:48 +02:00
log(f"""mail bulletin a {recipient_addr}""")
2022-03-04 20:02:50 +01:00
email.send_email(
subject,
sender,
recipients,
bcc=bcc,
2022-03-04 20:02:50 +01:00
text_body=hea,
attachments=[
{"filename": filename, "mimetype": scu.PDF_MIMETYPE, "data": pdfdata}
],
)
2020-09-26 16:19:37 +02:00
def make_menu_autres_operations(
formsemestre: FormSemestre, etud: Identite, endpoint: str, version: str
) -> str:
etud_email = etud.get_first_email() or ""
etud_perso = etud.get_first_email("emailperso") or ""
menu_items = [
2020-09-26 16:19:37 +02:00
{
"title": "Réglages bulletins",
2021-06-14 18:08:52 +02:00
"endpoint": "notes.formsemestre_edit_options",
"args": {
2022-03-05 12:47:08 +01:00
"formsemestre_id": formsemestre.id,
# "target_url": url_for(
# "notes.formsemestre_bulletinetud",
# scodoc_dept=g.scodoc_dept,
# formsemestre_id=formsemestre_id,
# etudid=etudid,
# ),
},
2022-03-05 12:47:08 +01:00
"enabled": formsemestre.can_be_edited_by(current_user),
2020-09-26 16:19:37 +02:00
},
{
"title": 'Version papier (pdf, format "%s")'
% sco_bulletins_generator.bulletin_get_class_name_displayed(
2022-03-05 12:47:08 +01:00
formsemestre.id
2020-09-26 16:19:37 +02:00
),
2021-06-14 18:08:52 +02:00
"endpoint": endpoint,
"args": {
2022-03-05 12:47:08 +01:00
"formsemestre_id": formsemestre.id,
"etudid": etud.id,
2021-06-14 18:08:52 +02:00
"version": version,
"fmt": "pdf",
2021-06-14 18:08:52 +02:00
},
2020-09-26 16:19:37 +02:00
},
{
"title": f"Envoi par mail à {etud_email}",
2021-06-14 18:08:52 +02:00
"endpoint": endpoint,
"args": {
2022-03-05 12:47:08 +01:00
"formsemestre_id": formsemestre.id,
"etudid": etud.id,
2021-06-14 18:08:52 +02:00
"version": version,
"fmt": "pdfmail",
2021-06-14 18:08:52 +02:00
},
2021-08-20 10:51:42 +02:00
# possible slt si on a un mail...
"enabled": etud_email and can_send_bulletin_by_mail(formsemestre.id),
2020-09-26 16:19:37 +02:00
},
{
"title": f"Envoi par mail à {etud_perso} (adr. personnelle)",
2021-06-14 18:08:52 +02:00
"endpoint": endpoint,
"args": {
2022-03-05 12:47:08 +01:00
"formsemestre_id": formsemestre.id,
"etudid": etud.id,
2021-06-14 18:08:52 +02:00
"version": version,
"fmt": "pdfmail",
2021-06-14 18:08:52 +02:00
"prefer_mail_perso": 1,
},
2021-08-20 10:51:42 +02:00
# possible slt si on a un mail...
"enabled": etud_perso and can_send_bulletin_by_mail(formsemestre.id),
2020-09-26 16:19:37 +02:00
},
{
"title": "Version json",
"endpoint": endpoint,
"args": {
2022-03-05 12:47:08 +01:00
"formsemestre_id": formsemestre.id,
"etudid": etud.id,
"version": version,
"fmt": "json",
},
},
2020-09-26 16:19:37 +02:00
{
"title": "Version XML",
2021-06-14 18:08:52 +02:00
"endpoint": endpoint,
"args": {
2022-03-05 12:47:08 +01:00
"formsemestre_id": formsemestre.id,
"etudid": etud.id,
2021-06-14 18:08:52 +02:00
"version": version,
"fmt": "xml",
2021-06-14 18:08:52 +02:00
},
2020-09-26 16:19:37 +02:00
},
{
"title": "Ajouter une appréciation",
2021-06-14 18:08:52 +02:00
"endpoint": "notes.appreciation_add_form",
"args": {
2022-03-05 12:47:08 +01:00
"formsemestre_id": formsemestre.id,
"etudid": etud.id,
2021-06-14 18:08:52 +02:00
},
2020-09-26 16:19:37 +02:00
"enabled": (
2022-03-05 12:47:08 +01:00
formsemestre.can_be_edited_by(current_user)
or current_user.has_permission(Permission.EtudInscrit)
2020-09-26 16:19:37 +02:00
),
},
{
"title": "Enregistrer un semestre effectué ailleurs",
2021-06-14 18:08:52 +02:00
"endpoint": "notes.formsemestre_ext_create_form",
"args": {
2022-03-05 12:47:08 +01:00
"formsemestre_id": formsemestre.id,
"etudid": etud.id,
2021-06-14 18:08:52 +02:00
},
"enabled": current_user.has_permission(Permission.EditFormSemestre),
2020-09-26 16:19:37 +02:00
},
{
"title": "Gérer les validations d'UEs antérieures",
2021-06-14 18:08:52 +02:00
"endpoint": "notes.formsemestre_validate_previous_ue",
"args": {
2022-03-05 12:47:08 +01:00
"formsemestre_id": formsemestre.id,
"etudid": etud.id,
2021-06-14 18:08:52 +02:00
},
"enabled": formsemestre.can_edit_jury(),
2020-09-26 16:19:37 +02:00
},
{
"title": "Enregistrer note d'une UE externe",
2021-06-14 18:08:52 +02:00
"endpoint": "notes.external_ue_create_form",
"args": {
2022-03-05 12:47:08 +01:00
"formsemestre_id": formsemestre.id,
"etudid": etud.id,
2021-06-14 18:08:52 +02:00
},
"enabled": formsemestre.can_edit_jury()
2022-12-18 03:14:13 +01:00
and not formsemestre.formation.is_apc(),
2020-09-26 16:19:37 +02:00
},
{
"title": "Entrer décisions jury",
2021-06-14 18:08:52 +02:00
"endpoint": "notes.formsemestre_validation_etud_form",
"args": {
2022-03-05 12:47:08 +01:00
"formsemestre_id": formsemestre.id,
"etudid": etud.id,
2021-06-14 18:08:52 +02:00
},
"enabled": formsemestre.can_edit_jury(),
2020-09-26 16:19:37 +02:00
},
{
2022-03-07 21:49:11 +01:00
"title": "Éditer PV jury",
2021-06-14 18:08:52 +02:00
"endpoint": "notes.formsemestre_pvjury_pdf",
"args": {
2022-03-05 12:47:08 +01:00
"formsemestre_id": formsemestre.id,
"etudid": etud.id,
2021-06-14 18:08:52 +02:00
},
2020-09-26 16:19:37 +02:00
"enabled": True,
},
]
return htmlutils.make_menu("Autres opérations", menu_items, alone=True)
2020-09-26 16:19:37 +02:00
def _formsemestre_bulletinetud_header_html(
etud,
formsemestre: FormSemestre,
fmt=None,
version=None,
):
H = [
html_sco_header.sco_header(
page_title=f"Bulletin de {etud.nomprenom}",
javascripts=[
"js/bulletin.js",
"libjs/d3.v3.min.js",
"js/radar_bulletin.js",
],
cssstyles=["css/radar_bulletin.css"],
),
render_template(
"bul_head.j2",
etud=etud,
fmt=fmt,
formsemestre=formsemestre,
menu_autres_operations=make_menu_autres_operations(
etud=etud,
formsemestre=formsemestre,
endpoint="notes.formsemestre_bulletinetud",
version=version,
),
scu=scu,
time=time,
version=version,
),
]
return "\n".join(H)