# -*- mode: python -*-
# -*- coding: utf-8 -*-
##############################################################################
#
# Gestion scolarite IUT
#
# Copyright (c) 1999 - 2023 Emmanuel Viennet. All rights reserved.
#
# 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
#
##############################################################################
"""Tableau de bord module
"""
import math
import time
from flask import g, url_for
from flask_login import current_user
from app import db
from app.auth.models import User
from app.comp import res_sem
from app.comp.res_common import ResultatsSemestre
from app.comp.res_compat import NotesTableCompat
from app.models import Evaluation, FormSemestre, Module, ModuleImpl, UniteEns
import app.scodoc.sco_utils as scu
from app.scodoc.codes_cursus import UE_SPORT
from app.scodoc.sco_exceptions import ScoInvalidIdType
from app.scodoc.sco_cursus_dut import formsemestre_has_decisions
from app.scodoc.sco_permissions import Permission
from app.scodoc import html_sco_header
from app.scodoc import htmlutils
from app.scodoc import sco_abs
from app.scodoc import sco_compute_moy
from app.scodoc import sco_evaluations
from app.scodoc import sco_evaluation_db
from app.scodoc import sco_formsemestre_status
from app.scodoc import sco_groups
from app.scodoc import sco_moduleimpl
from app.scodoc import sco_permissions_check
from app.tables import list_etuds
# menu evaluation dans moduleimpl
def moduleimpl_evaluation_menu(evaluation_id, nbnotes=0) -> str:
"Menu avec actions sur une evaluation"
E = sco_evaluation_db.do_evaluation_list({"evaluation_id": evaluation_id})[0]
modimpl = sco_moduleimpl.moduleimpl_list(moduleimpl_id=E["moduleimpl_id"])[0]
group_id = sco_groups.get_default_group(modimpl["formsemestre_id"])
if (
sco_permissions_check.can_edit_notes(
current_user, E["moduleimpl_id"], allow_ens=False
)
and nbnotes != 0
):
sup_label = "Supprimer évaluation impossible (il y a des notes)"
else:
sup_label = "Supprimer évaluation"
menu_eval = [
{
"title": "Saisir notes",
"endpoint": "notes.saisie_notes",
"args": {
"evaluation_id": evaluation_id,
},
"enabled": sco_permissions_check.can_edit_notes(
current_user, E["moduleimpl_id"]
),
},
{
"title": "Modifier évaluation",
"endpoint": "notes.evaluation_edit",
"args": {
"evaluation_id": evaluation_id,
},
"enabled": sco_permissions_check.can_edit_notes(
current_user, E["moduleimpl_id"], allow_ens=False
),
},
{
"title": sup_label,
"endpoint": "notes.evaluation_delete",
"args": {
"evaluation_id": evaluation_id,
},
"enabled": nbnotes == 0
and sco_permissions_check.can_edit_notes(
current_user, E["moduleimpl_id"], allow_ens=False
),
},
{
"title": "Supprimer toutes les notes",
"endpoint": "notes.evaluation_suppress_alln",
"args": {
"evaluation_id": evaluation_id,
},
"enabled": sco_permissions_check.can_edit_notes(
current_user, E["moduleimpl_id"], allow_ens=False
),
},
{
"title": "Afficher les notes",
"endpoint": "notes.evaluation_listenotes",
"args": {
"evaluation_id": evaluation_id,
},
"enabled": nbnotes > 0,
},
{
"title": "Placement étudiants",
"endpoint": "notes.placement_eval_selectetuds",
"args": {
"evaluation_id": evaluation_id,
},
"enabled": sco_permissions_check.can_edit_notes(
current_user, E["moduleimpl_id"]
),
},
{
"title": "Absences ce jour",
"endpoint": "assiduites.get_etat_abs_date",
"args": {
"group_ids": group_id,
"desc": E["description"],
"jour": E["jour"],
"heure_debut": E["heure_debut"],
"heure_fin": E["heure_fin"],
},
"enabled": E["jour"],
},
{
"title": "Vérifier notes vs absents",
"endpoint": "notes.evaluation_check_absences_html",
"args": {
"evaluation_id": evaluation_id,
},
"enabled": nbnotes > 0 and E["jour"],
},
]
return htmlutils.make_menu("actions", menu_eval, alone=True)
def _ue_coefs_html(coefs_lst) -> str:
""" """
max_coef = max([x[1] for x in coefs_lst]) if coefs_lst else 1.0
H = """
Coefficients vers les UE
"""
if coefs_lst:
H += (
f"""
"""
+ "\n".join(
[
f"""
"""
for ue, coef in coefs_lst
]
)
+ "
"
)
else:
H += """
non définis
"""
H += "
"
return H
def moduleimpl_status(moduleimpl_id=None, partition_id=None):
"""Tableau de bord module (liste des evaluations etc)"""
if not isinstance(moduleimpl_id, int):
raise ScoInvalidIdType("moduleimpl_id must be an integer !")
modimpl: ModuleImpl = ModuleImpl.query.get_or_404(moduleimpl_id)
g.current_moduleimpl_id = modimpl.id
module: Module = modimpl.module
formsemestre_id = modimpl.formsemestre_id
formsemestre: FormSemestre = modimpl.formsemestre
mod_inscrits = sco_moduleimpl.do_moduleimpl_inscription_list(
moduleimpl_id=moduleimpl_id
)
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
# Evaluations, la plus RECENTE en tête
evaluations = modimpl.evaluations.order_by(
Evaluation.numero.desc(),
Evaluation.jour.desc(),
Evaluation.heure_debut.desc(),
).all()
nb_evaluations = len(evaluations)
max_poids = max(
[
max([p.poids for p in e.ue_poids] or [0]) * (e.coefficient or 0.0)
for e in evaluations
]
or [0]
)
#
sem_locked = not formsemestre.etat
can_edit_evals = (
sco_permissions_check.can_edit_notes(
current_user, moduleimpl_id, allow_ens=formsemestre.ens_can_edit_eval
)
and not sem_locked
)
can_edit_notes = (
sco_permissions_check.can_edit_notes(current_user, moduleimpl_id)
and not sem_locked
)
arrow_up, arrow_down, arrow_none = sco_groups.get_arrow_icons_tags()
#
module_resp = db.session.get(User, modimpl.responsable_id)
mod_type_name = scu.MODULE_TYPE_NAMES[module.module_type]
H = [
html_sco_header.sco_header(
page_title=f"{mod_type_name} {module.code} {module.titre}",
javascripts=["js/etud_info.js"],
init_qtip=True,
),
f"""
Responsable: |
{module_resp.get_nomcomplet()}
({module_resp.user_name})
""",
]
if modimpl.can_change_ens_by(current_user):
H.append(
f"""modifier"""
)
H.append(""" | """)
H.append(", ".join([u.get_nomprenom() for u in modimpl.enseignants]))
H.append(""" | """)
try:
sco_moduleimpl.can_change_ens(moduleimpl_id)
H.append(
"""modifier les enseignants"""
% moduleimpl_id
)
except:
pass
H.append(""" |
""")
# 2ieme ligne: Semestre, Coef
H.append("""""")
if formsemestre.semestre_id >= 0:
H.append("""Semestre: | %s""" % formsemestre.semestre_id)
else:
H.append(""" | """)
if sem_locked:
H.append(scu.icontag("lock32_img", title="verrouillé"))
H.append(""" | """)
if modimpl.module.is_apc():
H.append(_ue_coefs_html(modimpl.module.ue_coefs_list()))
else:
H.append(
f"""Coef. dans le semestre: {
"non défini" if modimpl.module.coefficient is None else modimpl.module.coefficient
}"""
)
H.append(""" | |
""")
# 3ieme ligne: Formation
H.append(
f"""
Formation: | {formsemestre.formation.titre} |
"""
)
# Ligne: Inscrits
H.append(
f"""Inscrits: | {len(mod_inscrits)} étudiants"""
)
if current_user.has_permission(Permission.ScoEtudInscrit):
H.append(
f"""modifier"""
)
H.append(" |
")
# Ligne: règle de calcul
has_expression = sco_compute_moy.moduleimpl_has_expression(modimpl)
if has_expression:
H.append(
f"""
Règle de calcul:
moyenne={modimpl.computation_expr}
"""
)
H.append("""inutilisée dans cette version de ScoDoc""")
if sco_moduleimpl.can_change_ens(moduleimpl_id, raise_exc=False):
H.append(
f""" supprimer"""
)
H.append("""""")
H.append(" |
")
else:
H.append(
''
# règle de calcul standard'
)
# if sco_moduleimpl.can_change_ens(moduleimpl_id, raise_exc=False):
# H.append(
# f' (changer)'
# )
H.append(" |
")
H.append(
f"""Absences dans ce module"""
)
# Adapté à partir d'une suggestion de DS (Le Havre)
# Liens saisies absences seulement si permission et date courante dans le semestre
if (
current_user.has_permission(Permission.ScoAbsChange)
and formsemestre.est_courant()
):
datelundi = sco_abs.ddmmyyyy(time.strftime("%d/%m/%Y")).prev_monday()
group_id = sco_groups.get_default_group(formsemestre_id)
H.append(
f"""
Saisie Absences hebdo.
"""
)
H.append(" |
")
#
if not modimpl.check_apc_conformity(nt):
H.append(
"""
Les poids des évaluations de ce module ne sont
pas encore conformes au PN.
Ses notes ne peuvent pas être prises en compte dans les moyennes d'UE.
"""
)
#
if has_expression and nt.expr_diagnostics:
H.append(sco_formsemestre_status.html_expr_diagnostic(nt.expr_diagnostics))
#
if formsemestre_has_decisions(formsemestre_id):
H.append(
"""
- Décisions de jury saisies: seul le responsable du
semestre peut saisir des notes (il devra modifier les décisions de jury).
"""
)
#
H.append(
f"""
"""
)
# -------- Tableau des evaluations
top_table_links = ""
if can_edit_evals:
top_table_links = f"""
Créer nouvelle évaluation
"""
if nb_evaluations > 0:
top_table_links += f"""
Trier par date
"""
if nb_evaluations > 0:
H.append(
'
'
+ top_table_links
+ "
"
)
H.append("""
""")
eval_index = nb_evaluations - 1
first_eval = True
for evaluation in evaluations:
H.append(
_ligne_evaluation(
modimpl,
evaluation,
first_eval=first_eval,
partition_id=partition_id,
arrow_down=arrow_down,
arrow_none=arrow_none,
arrow_up=arrow_up,
can_edit_evals=can_edit_evals,
can_edit_notes=can_edit_notes,
eval_index=eval_index,
nb_evals=nb_evaluations,
is_apc=nt.is_apc,
max_poids=max_poids,
)
)
eval_index -= 1
first_eval = False
#
H.append("""""")
if sem_locked:
H.append(f"""{scu.icontag("lock32_img")} semestre verrouillé""")
elif can_edit_evals:
H.append(top_table_links)
H.append(
f""" |
{_html_modimpl_etuds_attente(nt, modimpl)}
Légende
- {scu.icontag("edit_img")} : modifie description de l'évaluation
(date, heure, coefficient, ...)
- {scu.icontag("notes_img")} : saisie des notes
- {scu.icontag("delete_img")} : indique qu'il n'y a aucune note
entrée (cliquer pour supprimer cette évaluation)
- {scu.icontag("status_orange_img")} : indique qu'il manque
quelques notes dans cette évaluation
- {scu.icontag("status_green_img")} : toutes les notes sont
entrées (cliquer pour les afficher)
- {scu.icontag("status_visible_img")} : indique que cette évaluation
sera mentionnée dans les bulletins au format "intermédiaire"
Rappel : seules les notes des évaluations complètement saisies
(affichées en vert) apparaissent dans les bulletins.
"""
)
H.append(html_sco_header.sco_footer())
return "".join(H)
def _ligne_evaluation(
modimpl: ModuleImpl,
evaluation: Evaluation,
first_eval: bool = True,
partition_id: int = None,
arrow_down=None,
arrow_none=None,
arrow_up=None,
can_edit_evals: bool = False,
can_edit_notes: bool = False,
eval_index: int = 0,
nb_evals: int = 0,
is_apc: bool = False,
max_poids: float = 0.0,
) -> str:
"""Ligne décrivant une évaluation dans le tableau de bord moduleimpl."""
H = []
# evaluation: Evaluation = db.session.get(Evaluation, eval_dict["evaluation_id"])
etat = sco_evaluations.do_evaluation_etat(
evaluation.id,
partition_id=partition_id,
select_first_partition=True,
)
if evaluation.evaluation_type in (
scu.EVALUATION_RATTRAPAGE,
scu.EVALUATION_SESSION2,
):
tr_class = "mievr mievr_rattr"
else:
tr_class = "mievr"
tr_class_1 = "mievr"
if not first_eval:
H.append("""
|
""")
tr_class_1 += " mievr_spaced"
H.append(
f""""""
)
coef = evaluation.coefficient
if is_apc:
if not evaluation.get_ue_poids_dict():
# Au cas où les poids par défaut n'existent pas encore:
if evaluation.set_default_poids():
db.session.commit()
coef *= sum(evaluation.get_ue_poids_dict().values())
if modimpl.module.ue.type != UE_SPORT:
# Avertissement si coefs x poids nuls
if coef < scu.NOTES_PRECISION:
H.append("""coef. nul !""")
elif is_apc:
# visualisation des poids (Hinton map)
H.append(_evaluation_poids_html(evaluation, max_poids))
H.append("""""")
if evaluation.jour:
H.append(
f"""Le {evaluation.jour.strftime("%d/%m/%Y")} {evaluation.descr_heure()}"""
)
else:
H.append(
f""" Évaluation sans date"""
)
H.append(f" {evaluation.description or ''}")
if evaluation.evaluation_type == scu.EVALUATION_RATTRAPAGE:
H.append(
""" rattrapage"""
)
elif evaluation.evaluation_type == scu.EVALUATION_SESSION2:
H.append(
""" session 2"""
)
#
if etat["last_modif"]:
H.append(
f""" (dernière modif le {
etat["last_modif"].strftime("%d/%m/%Y à %Hh%M")})"""
)
#
H.append(
f"""
|
{
eval_index:2}
"""
)
# Fleches:
if eval_index != (nb_evals - 1) and can_edit_evals:
H.append(
f"""{arrow_up}"""
)
else:
H.append(arrow_none)
if (eval_index > 0) and can_edit_evals:
H.append(
f"""{arrow_down}"""
)
else:
H.append(arrow_none)
if etat["evalcomplete"]:
etat_txt = """(prise en compte)"""
etat_descr = "notes utilisées dans les moyennes"
elif etat["evalattente"] and not evaluation.publish_incomplete:
etat_txt = "(prise en compte, mais notes en attente)"
etat_descr = "il y a des notes en attente"
elif evaluation.publish_incomplete:
etat_txt = """(prise en compte immédiate)"""
etat_descr = (
"il manque des notes, mais la prise en compte immédiate a été demandée"
)
elif etat["nb_notes"] != 0:
etat_txt = "(non prise en compte)"
etat_descr = "il manque des notes"
else:
etat_txt = ""
if can_edit_evals and etat_txt:
etat_txt = f"""{etat_txt}"""
H.append(
f""" |
|
Durée |
Coef. |
Notes |
Abs |
N |
Moyenne {etat_txt} |
"""
)
if can_edit_evals:
H.append(
f"""{scu.icontag("edit_img", alt="modifier", title="Modifier informations")}"""
)
if can_edit_notes:
H.append(
f"""{scu.icontag("notes_img", alt="saisie notes", title="Saisie des notes")}"""
)
if etat["nb_notes"] == 0:
if can_edit_evals:
H.append(
f""""""
)
H.append(scu.icontag("delete_img", alt="supprimer", title="Supprimer"))
if can_edit_evals:
H.append("""""")
elif etat["evalcomplete"]:
H.append(
f"""{scu.icontag("status_green_img", title="ok")}"""
)
else:
if etat["evalattente"]:
H.append(
f"""{scu.icontag(
"status_greenorange_img",
file_format="gif",
title="notes en attente",
)}"""
)
else:
H.append(
f"""{scu.icontag("status_orange_img", title="il manque des notes")}"""
)
#
if evaluation.visibulletin:
H.append(
scu.icontag(
"status_visible_img", title="visible dans bulletins intermédiaires"
)
)
else:
H.append(" ")
H.append(' |
{evaluation.descr_duree()} |
{evaluation.coefficient:g} |
"""
)
H.append(
f"""
{etat["nb_notes"]} / {etat["nb_inscrits"]} |
{etat["nb_abs"]} |
{etat["nb_neutre"]} |
"""
% etat
)
if etat["moy"]:
H.append(
f"""{etat["moy"]} / 20
(afficher)"""
)
else:
H.append(
f"""saisir notes
"""
)
H.append(""" |
""")
#
if etat["nb_notes"] == 0:
H.append(f""" | """)
if modimpl.module.is_apc():
H.append(
f"""{
evaluation.get_ue_poids_str()} | """
)
else:
H.append(' | ')
H.append("""
""")
else: # il y a deja des notes saisies
gr_moyennes = etat["gr_moyennes"]
first_group = True
for gr_moyenne in gr_moyennes:
H.append(f""" | """)
if first_group and modimpl.module.is_apc():
H.append(
f"""{
evaluation.get_ue_poids_str()} | """
)
else:
H.append(""" | """)
first_group = False
if gr_moyenne["group_name"] is None:
name = "Tous" # tous
else:
name = f"""Groupe {gr_moyenne["group_name"]}"""
H.append(
f"""{name} |
"""
)
if gr_moyenne["gr_nb_notes"] > 0:
H.append(
f"""{gr_moyenne["gr_moy"]} ({gr_moyenne["gr_nb_notes"]} notes"""
)
if gr_moyenne["gr_nb_att"] > 0:
H.append(
f""", {
gr_moyenne["gr_nb_att"]} en attente"""
)
H.append(""")""")
if gr_moyenne["group_id"] in etat["gr_incomplets"]:
H.append("""[""")
if can_edit_notes:
H.append(
f"""incomplet]"""
)
else:
H.append("""incomplet]""")
else:
H.append(""" """)
if can_edit_notes:
H.append(
f""""""
)
H.append("pas de notes")
if can_edit_notes:
H.append("""""")
H.append("")
H.append(""" |
""")
return "\n".join(H)
def _evaluation_poids_html(evaluation: Evaluation, max_poids: float = 0.0) -> str:
"""graphe html (Hinton map) montrant les poids x coef de l'évaluation"""
ue_poids = evaluation.get_ue_poids_dict(sort=True) # { ue_id : poids }
if not ue_poids:
return ""
if max_poids < scu.NOTES_PRECISION:
return ""
H = (
""""""
+ "\n".join(
[
f"""
"""
for ue, poids in (
(db.session.get(UniteEns, ue_id), poids)
for ue_id, poids in ue_poids.items()
)
]
)
+ "
"
)
return H
def _html_modimpl_etuds_attente(res: ResultatsSemestre, modimpl: ModuleImpl) -> str:
"""Affiche la liste des étudiants ayant au moins une note en attente dans ce modimpl"""
m_res = res.modimpls_results.get(modimpl.id)
if m_res:
if not m_res.etudids_attente:
return "Aucun étudiant n'a de notes en attente.
"
elif len(m_res.etudids_attente) < 10:
return f"""
Étudiants avec une note en attente :
{list_etuds.html_table_etuds(m_res.etudids_attente)}
"""
else:
return f"""{
len(m_res.etudids_attente)
} étudiants ont des notes en attente.
"""
return ""