1499 lines
56 KiB
Python
Executable File
1499 lines
56 KiB
Python
Executable File
# -*- mode: python -*-
|
|
# -*- coding: utf-8 -*-
|
|
|
|
##############################################################################
|
|
#
|
|
# Gestion scolarite IUT
|
|
#
|
|
# Copyright (c) 1999 - 2024 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 semestre
|
|
"""
|
|
|
|
import datetime
|
|
|
|
from flask import current_app
|
|
from flask import g
|
|
from flask import request
|
|
from flask import flash, redirect, render_template, url_for
|
|
from flask_login import current_user
|
|
|
|
from app import db, log
|
|
from app.but.cursus_but import formsemestre_warning_apc_setup
|
|
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,
|
|
Formation,
|
|
FormSemestre,
|
|
Identite,
|
|
Module,
|
|
ModuleImpl,
|
|
NotesNotes,
|
|
)
|
|
from app.scodoc.codes_cursus import UE_SPORT
|
|
from app.scodoc.sco_exceptions import (
|
|
ScoValueError,
|
|
ScoInvalidIdType,
|
|
)
|
|
from app.scodoc.sco_permissions import Permission
|
|
import app.scodoc.sco_utils as scu
|
|
from app.scodoc.sco_utils import ModuleType
|
|
|
|
from app.scodoc import html_sco_header
|
|
from app.scodoc import htmlutils
|
|
from app.scodoc import sco_archives_formsemestre
|
|
from app.scodoc import sco_bulletins
|
|
from app.scodoc import codes_cursus
|
|
from app.scodoc import sco_compute_moy
|
|
from app.scodoc import sco_edit_ue
|
|
from app.scodoc import sco_evaluations
|
|
from app.scodoc import sco_evaluation_db
|
|
from app.scodoc import sco_formations
|
|
from app.scodoc import sco_formsemestre
|
|
from app.scodoc import sco_formsemestre_inscriptions
|
|
from app.scodoc import sco_groups
|
|
from app.scodoc import sco_moduleimpl
|
|
from app.scodoc import sco_preferences
|
|
from app.scodoc import sco_users
|
|
from app.scodoc.gen_tables import GenTable
|
|
from app.scodoc.html_sidebar import retreive_formsemestre_from_request
|
|
from app.scodoc.sco_formsemestre_custommenu import formsemestre_custommenu_html
|
|
import sco_version
|
|
|
|
|
|
def _build_menu_stats(formsemestre: FormSemestre):
|
|
"Définition du menu 'Statistiques'"
|
|
formsemestre_id = formsemestre.id
|
|
return [
|
|
{
|
|
"title": "Statistiques...",
|
|
"endpoint": "notes.formsemestre_report_counts",
|
|
"args": {"formsemestre_id": formsemestre_id},
|
|
},
|
|
{
|
|
"title": "Suivi de cohortes",
|
|
"endpoint": "notes.formsemestre_suivi_cohorte",
|
|
"args": {"formsemestre_id": formsemestre_id},
|
|
"enabled": True,
|
|
},
|
|
{
|
|
"title": "Graphe des cursus",
|
|
"endpoint": "notes.formsemestre_graph_cursus",
|
|
"args": {"formsemestre_id": formsemestre_id},
|
|
"enabled": True,
|
|
},
|
|
{
|
|
"title": "Codes des cursus",
|
|
"endpoint": "notes.formsemestre_suivi_cursus",
|
|
"args": {"formsemestre_id": formsemestre_id},
|
|
"enabled": True,
|
|
},
|
|
{
|
|
"title": "Lycées d'origine",
|
|
"endpoint": "notes.formsemestre_etuds_lycees",
|
|
"args": {"formsemestre_id": formsemestre_id},
|
|
"enabled": current_user.has_permission(Permission.ViewEtudData),
|
|
},
|
|
{
|
|
"title": 'Table "poursuite"',
|
|
"endpoint": "notes.formsemestre_poursuite_report",
|
|
"args": {"formsemestre_id": formsemestre_id},
|
|
"enabled": True,
|
|
},
|
|
{
|
|
"title": "Documents Avis Poursuite Etudes (xp)",
|
|
"endpoint": "notes.pe_view_sem_recap",
|
|
"args": {"formsemestre_id": formsemestre_id},
|
|
"enabled": formsemestre.formation.is_apc(),
|
|
# current_app.config["TESTING"] or current_app.config["DEBUG"],
|
|
},
|
|
{
|
|
"title": 'Table "débouchés"',
|
|
"endpoint": "notes.report_debouche_date",
|
|
"enabled": True,
|
|
},
|
|
{
|
|
"title": "Estimation du coût de la formation",
|
|
"endpoint": "notes.formsemestre_estim_cost",
|
|
"args": {"formsemestre_id": formsemestre_id},
|
|
"enabled": True,
|
|
},
|
|
{
|
|
"title": "Indicateurs de suivi annuel BUT",
|
|
"endpoint": "notes.formsemestre_but_indicateurs",
|
|
"args": {"formsemestre_id": formsemestre_id},
|
|
"enabled": True,
|
|
},
|
|
]
|
|
|
|
|
|
def formsemestre_status_menubar(formsemestre: FormSemestre) -> str:
|
|
"""HTML to render menubar"""
|
|
formsemestre_id = formsemestre.id
|
|
if formsemestre.etat:
|
|
change_lock_msg = "Verrouiller"
|
|
else:
|
|
change_lock_msg = "Déverrouiller"
|
|
|
|
formation = formsemestre.formation
|
|
|
|
# L'utilisateur est-il resp. du semestre ?
|
|
is_responsable = current_user.id in (u.id for u in formsemestre.responsables)
|
|
# A le droit de changer le semestre (déverrouiller, préférences bul., ...):
|
|
has_perm_change_sem = current_user.has_permission(Permission.EditFormSemestre) or (
|
|
formsemestre.resp_can_edit and is_responsable
|
|
)
|
|
# Peut modifier le semestre (si n'est pas verrouillé):
|
|
can_modify_sem = has_perm_change_sem and formsemestre.etat
|
|
|
|
menu_semestre = [
|
|
{
|
|
"title": "Tableau de bord",
|
|
"endpoint": "notes.formsemestre_status",
|
|
"args": {"formsemestre_id": formsemestre_id},
|
|
"enabled": True,
|
|
"helpmsg": "Tableau de bord du semestre",
|
|
},
|
|
# {
|
|
# "title": "Assiduité du semestre",
|
|
# "endpoint": "assiduites.liste_assiduites_formsemestre",
|
|
# "args": {"formsemestre_id": formsemestre_id},
|
|
# "enabled": True,
|
|
# "helpmsg": "Tableau de l'assiduité et des justificatifs du semestre",
|
|
# },
|
|
{
|
|
"title": f"Voir la formation {formation.acronyme} (v{formation.version})",
|
|
"endpoint": "notes.ue_table",
|
|
"args": {
|
|
"formation_id": formation.id,
|
|
"semestre_idx": formsemestre.semestre_id,
|
|
},
|
|
"enabled": True,
|
|
"helpmsg": "Tableau de bord du semestre",
|
|
},
|
|
{
|
|
"title": "Modifier le semestre",
|
|
"endpoint": "notes.formsemestre_editwithmodules",
|
|
"args": {
|
|
"formsemestre_id": formsemestre_id,
|
|
},
|
|
"enabled": can_modify_sem,
|
|
"helpmsg": "Modifie le contenu du semestre (modules)",
|
|
},
|
|
{
|
|
"title": "Préférences du semestre",
|
|
"endpoint": "scolar.formsemestre_edit_preferences",
|
|
"args": {"formsemestre_id": formsemestre_id},
|
|
"enabled": can_modify_sem,
|
|
"helpmsg": "Préférences du semestre",
|
|
},
|
|
{
|
|
"title": "Réglages bulletins",
|
|
"endpoint": "notes.formsemestre_edit_options",
|
|
"args": {"formsemestre_id": formsemestre_id},
|
|
"enabled": has_perm_change_sem,
|
|
"helpmsg": "Change les options",
|
|
},
|
|
{
|
|
"title": change_lock_msg,
|
|
"endpoint": "notes.formsemestre_flip_lock",
|
|
"args": {"formsemestre_id": formsemestre_id},
|
|
"enabled": has_perm_change_sem,
|
|
"helpmsg": "",
|
|
},
|
|
{
|
|
"title": "Description du semestre",
|
|
"endpoint": "notes.formsemestre_description",
|
|
"args": {"formsemestre_id": formsemestre_id},
|
|
"enabled": True,
|
|
"helpmsg": "",
|
|
},
|
|
{
|
|
"title": "Lister tous les enseignants",
|
|
"endpoint": "notes.formsemestre_enseignants_list",
|
|
"args": {"formsemestre_id": formsemestre_id},
|
|
"enabled": True,
|
|
"helpmsg": "",
|
|
},
|
|
{
|
|
"title": "Cloner ce semestre",
|
|
"endpoint": "notes.formsemestre_clone",
|
|
"args": {"formsemestre_id": formsemestre_id},
|
|
"enabled": current_user.has_permission(Permission.EditFormSemestre),
|
|
"helpmsg": "",
|
|
},
|
|
{
|
|
"title": "Associer à une nouvelle version du programme",
|
|
"endpoint": "notes.formsemestre_associate_new_version",
|
|
"args": {
|
|
"formsemestre_id": formsemestre_id,
|
|
"formation_id": formsemestre.formation_id,
|
|
},
|
|
"enabled": current_user.has_permission(Permission.EditFormation)
|
|
and formsemestre.etat,
|
|
"helpmsg": "",
|
|
},
|
|
{
|
|
"title": "Supprimer ce semestre",
|
|
"endpoint": "notes.formsemestre_delete",
|
|
"args": {"formsemestre_id": formsemestre_id},
|
|
"enabled": current_user.has_permission(Permission.EditFormSemestre),
|
|
"helpmsg": "",
|
|
},
|
|
{
|
|
"title": "Expérimental: emploi du temps",
|
|
"endpoint": "notes.formsemestre_edt",
|
|
"args": {"formsemestre_id": formsemestre_id},
|
|
"enabled": True,
|
|
"helpmsg": "",
|
|
},
|
|
]
|
|
# debug :
|
|
if current_app.config["DEBUG"]:
|
|
menu_semestre.append(
|
|
{
|
|
"title": "Vérifier l'intégrité",
|
|
"endpoint": "notes.check_sem_integrity",
|
|
"args": {"formsemestre_id": formsemestre_id},
|
|
"enabled": True,
|
|
}
|
|
)
|
|
|
|
menu_inscriptions = [
|
|
{
|
|
"title": (
|
|
"Gérer les inscriptions aux UE et modules"
|
|
if formsemestre.formation.is_apc()
|
|
else "Gérer les inscriptions aux modules"
|
|
),
|
|
"endpoint": "notes.moduleimpl_inscriptions_stats",
|
|
"args": {"formsemestre_id": formsemestre_id},
|
|
}
|
|
]
|
|
menu_inscriptions += [
|
|
{
|
|
"title": "Passage des étudiants depuis d'autres semestres",
|
|
"endpoint": "notes.formsemestre_inscr_passage",
|
|
"args": {"formsemestre_id": formsemestre_id},
|
|
"enabled": current_user.has_permission(Permission.EtudInscrit)
|
|
and formsemestre.etat,
|
|
},
|
|
{
|
|
"title": "Synchroniser avec étape Apogée",
|
|
"endpoint": "notes.formsemestre_synchro_etuds",
|
|
"args": {"formsemestre_id": formsemestre_id},
|
|
"enabled": current_user.has_permission(Permission.ScoView)
|
|
and sco_preferences.get_preference("portal_url")
|
|
and formsemestre.etat,
|
|
},
|
|
{
|
|
"title": "Inscrire un étudiant",
|
|
"endpoint": "notes.formsemestre_inscription_with_modules_etud",
|
|
"args": {"formsemestre_id": formsemestre_id},
|
|
"enabled": current_user.has_permission(Permission.EtudInscrit)
|
|
and formsemestre.etat,
|
|
},
|
|
{
|
|
"title": "Importer des étudiants dans ce semestre (table Excel)",
|
|
"endpoint": "scolar.form_students_import_excel",
|
|
"args": {"formsemestre_id": formsemestre_id},
|
|
"enabled": current_user.has_permission(Permission.EtudInscrit)
|
|
and formsemestre.etat,
|
|
},
|
|
{
|
|
"title": "Import/export des données admission",
|
|
"endpoint": "scolar.form_students_import_infos_admissions",
|
|
"args": {"formsemestre_id": formsemestre_id},
|
|
"enabled": current_user.has_permission(Permission.ScoView),
|
|
},
|
|
{
|
|
"title": "Resynchroniser données identité",
|
|
"endpoint": "scolar.formsemestre_import_etud_admission",
|
|
"args": {"formsemestre_id": formsemestre_id},
|
|
"enabled": current_user.has_permission(Permission.EtudChangeAdr)
|
|
and sco_preferences.get_preference("portal_url"),
|
|
},
|
|
{
|
|
"title": "Exporter table des étudiants",
|
|
"endpoint": "scolar.groups_view",
|
|
"args": {
|
|
"fmt": "allxls",
|
|
"group_ids": sco_groups.get_default_group(
|
|
formsemestre_id, fix_if_missing=True
|
|
),
|
|
},
|
|
"enabled": current_user.has_permission(Permission.ViewEtudData),
|
|
},
|
|
{
|
|
"title": "Vérifier inscriptions multiples",
|
|
"endpoint": "notes.formsemestre_inscrits_ailleurs",
|
|
"args": {"formsemestre_id": formsemestre_id},
|
|
},
|
|
]
|
|
|
|
can_change_groups = formsemestre.can_change_groups()
|
|
menu_groupes = [
|
|
{
|
|
"title": "Listes, photos, feuilles...",
|
|
"endpoint": "scolar.groups_view",
|
|
"args": {"formsemestre_id": formsemestre_id},
|
|
"enabled": True,
|
|
"helpmsg": "Accès aux listes des groupes d'étudiants",
|
|
},
|
|
{
|
|
"title": "Modifier groupes et partitions",
|
|
"endpoint": "scolar.partition_editor",
|
|
"args": {"formsemestre_id": formsemestre_id},
|
|
"enabled": can_change_groups,
|
|
"helpmsg": "Editeur de partitions",
|
|
},
|
|
{
|
|
"title": "Ancienne page édition partitions",
|
|
"endpoint": "scolar.edit_partition_form",
|
|
"args": {"formsemestre_id": formsemestre_id},
|
|
"enabled": can_change_groups,
|
|
},
|
|
]
|
|
# 1 item / partition:
|
|
partitions = sco_groups.get_partitions_list(formsemestre_id, with_default=False)
|
|
submenu = []
|
|
enabled = can_change_groups and partitions
|
|
for partition in partitions:
|
|
submenu.append(
|
|
{
|
|
"title": str(partition["partition_name"]),
|
|
"endpoint": "scolar.affect_groups",
|
|
"args": {"partition_id": partition["partition_id"]},
|
|
"enabled": enabled,
|
|
}
|
|
)
|
|
menu_groupes.append(
|
|
{
|
|
"title": "Ancienne page édition groupes",
|
|
"submenu": submenu,
|
|
"enabled": enabled,
|
|
}
|
|
)
|
|
|
|
menu_notes = [
|
|
{
|
|
"title": "Tableau des moyennes (et liens bulletins)",
|
|
"endpoint": "notes.formsemestre_recapcomplet",
|
|
"args": {"formsemestre_id": formsemestre_id},
|
|
},
|
|
{
|
|
"title": "État des évaluations",
|
|
"endpoint": "notes.evaluations_recap",
|
|
"args": {"formsemestre_id": formsemestre_id},
|
|
},
|
|
{
|
|
"title": "Classeur PDF des bulletins",
|
|
"endpoint": "notes.formsemestre_bulletins_pdf_choice",
|
|
"args": {"formsemestre_id": formsemestre_id},
|
|
"helpmsg": "PDF regroupant tous les bulletins",
|
|
},
|
|
{
|
|
"title": "Envoyer à chaque étudiant son bulletin par e-mail",
|
|
"endpoint": "notes.formsemestre_bulletins_mailetuds_choice",
|
|
"args": {"formsemestre_id": formsemestre_id},
|
|
"enabled": sco_bulletins.can_send_bulletin_by_mail(formsemestre_id),
|
|
},
|
|
{
|
|
"title": "Calendrier des évaluations",
|
|
"endpoint": "notes.formsemestre_evaluations_cal",
|
|
"args": {"formsemestre_id": formsemestre_id},
|
|
},
|
|
{
|
|
"title": "Lister toutes les saisies de notes",
|
|
"endpoint": "notes.formsemestre_list_saisies_notes",
|
|
"args": {"formsemestre_id": formsemestre_id},
|
|
},
|
|
]
|
|
menu_jury = [
|
|
{
|
|
"title": "Voir les décisions du jury",
|
|
"endpoint": "notes.formsemestre_pvjury",
|
|
"args": {"formsemestre_id": formsemestre_id},
|
|
},
|
|
{
|
|
"title": "Saisie des décisions du jury",
|
|
"endpoint": "notes.formsemestre_recapcomplet",
|
|
"args": {
|
|
"formsemestre_id": formsemestre_id,
|
|
"mode_jury": 1,
|
|
},
|
|
"enabled": formsemestre.can_edit_jury(),
|
|
},
|
|
{
|
|
"title": "Générer feuille préparation Jury (non BUT)",
|
|
"endpoint": "notes.feuille_preparation_jury",
|
|
"args": {"formsemestre_id": formsemestre_id},
|
|
"enabled": not formsemestre.formation.is_apc(),
|
|
},
|
|
{
|
|
"title": "Éditer les PV et archiver les résultats",
|
|
"endpoint": "notes.formsemestre_archive",
|
|
"args": {"formsemestre_id": formsemestre_id},
|
|
"enabled": formsemestre.can_edit_pv(),
|
|
},
|
|
{
|
|
"title": "Documents archivés",
|
|
"endpoint": "notes.formsemestre_list_archives",
|
|
"args": {"formsemestre_id": formsemestre_id},
|
|
"enabled": sco_archives_formsemestre.PV_ARCHIVER.list_obj_archives(
|
|
formsemestre_id
|
|
),
|
|
},
|
|
]
|
|
|
|
menu_stats = _build_menu_stats(formsemestre)
|
|
H = [
|
|
'<ul id="sco_menu">',
|
|
htmlutils.make_menu("Semestre", menu_semestre),
|
|
htmlutils.make_menu("Inscriptions", menu_inscriptions),
|
|
htmlutils.make_menu("Groupes", menu_groupes),
|
|
htmlutils.make_menu("Notes", menu_notes),
|
|
htmlutils.make_menu("Jury", menu_jury),
|
|
htmlutils.make_menu("Statistiques", menu_stats),
|
|
formsemestre_custommenu_html(formsemestre_id),
|
|
"</ul>",
|
|
]
|
|
return "\n".join(H)
|
|
|
|
|
|
# Element HTML decrivant un semestre (barre de menu et infos)
|
|
def formsemestre_page_title(formsemestre_id=None):
|
|
"""Element HTML decrivant un semestre (barre de menu et infos)
|
|
Cherche dans la requete si un semestre est défini
|
|
via (formsemestre_id ou moduleimpl ou evaluation ou group)
|
|
"""
|
|
formsemestre_id = (
|
|
formsemestre_id
|
|
if formsemestre_id is not None
|
|
else retreive_formsemestre_from_request()
|
|
)
|
|
#
|
|
if not formsemestre_id:
|
|
return ""
|
|
try:
|
|
formsemestre_id = int(formsemestre_id)
|
|
except ValueError:
|
|
log(f"formsemestre_id: invalid type {formsemestre_id:r}")
|
|
return ""
|
|
formsemestre = FormSemestre.get_formsemestre(formsemestre_id)
|
|
|
|
return render_template(
|
|
"formsemestre_page_title.j2",
|
|
formsemestre=formsemestre,
|
|
scu=scu,
|
|
sem_menu_bar=formsemestre_status_menubar(formsemestre),
|
|
)
|
|
|
|
|
|
# ---------
|
|
# ancienne fonction ScoDoc7 à supprimer lorsqu'on utilisera les modèles
|
|
# utilisé seulement par export Apogée
|
|
def fill_formsemestre(sem: dict): # XXX OBSOLETE
|
|
"""Add some fields in formsemestres dicts"""
|
|
formsemestre_id = sem["formsemestre_id"]
|
|
|
|
F = sco_formations.formation_list(args={"formation_id": sem["formation_id"]})[0]
|
|
sem["formation"] = F
|
|
parcours = codes_cursus.get_cursus_from_code(F["type_parcours"])
|
|
if sem["semestre_id"] != -1:
|
|
sem["num_sem"] = f""", {parcours.SESSION_NAME} {sem["semestre_id"]}"""
|
|
else:
|
|
sem["num_sem"] = "" # formation sans semestres
|
|
if sem["modalite"]:
|
|
sem["modalitestr"] = f""" en {sem["modalite"]}"""
|
|
else:
|
|
sem["modalitestr"] = ""
|
|
|
|
sem["etape_apo_str"] = "Code étape Apogée: " + (
|
|
sco_formsemestre.formsemestre_etape_apo_str(sem) or "Pas de code étape"
|
|
)
|
|
|
|
inscrits = sco_formsemestre_inscriptions.do_formsemestre_inscription_list(
|
|
args={"formsemestre_id": formsemestre_id}
|
|
)
|
|
sem["nbinscrits"] = len(inscrits)
|
|
uresps = [
|
|
sco_users.user_info(responsable_id) for responsable_id in sem["responsables"]
|
|
]
|
|
sem["resp"] = ", ".join([u["prenomnom"] for u in uresps])
|
|
sem["nomcomplet"] = ", ".join([u["nomcomplet"] for u in uresps])
|
|
|
|
|
|
# Description du semestre sous forme de table exportable
|
|
def formsemestre_description_table(
|
|
formsemestre_id: int, with_evals=False, with_parcours=False
|
|
):
|
|
"""Description du semestre sous forme de table exportable
|
|
Liste des modules et de leurs coefficients
|
|
"""
|
|
formsemestre: FormSemestre = FormSemestre.query.filter_by(
|
|
id=formsemestre_id, dept_id=g.scodoc_dept_id
|
|
).first_or_404()
|
|
is_apc = formsemestre.formation.is_apc()
|
|
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
|
|
use_ue_coefs = sco_preferences.get_preference("use_ue_coefs", formsemestre_id)
|
|
parcours = codes_cursus.get_cursus_from_code(formsemestre.formation.type_parcours)
|
|
# --- Colonnes à proposer:
|
|
columns_ids = ["UE", "Code", "Module"]
|
|
if with_parcours:
|
|
columns_ids += ["parcours"]
|
|
if not formsemestre.formation.is_apc():
|
|
columns_ids += ["Coef."]
|
|
ues = [] # liste des UE, seulement en APC pour les coefs
|
|
else:
|
|
ues = formsemestre.get_ues()
|
|
columns_ids += [f"ue_{ue.id}" for ue in ues]
|
|
if sco_preferences.get_preference("bul_show_ects", formsemestre_id) and not is_apc:
|
|
columns_ids += ["ects"]
|
|
columns_ids += ["Inscrits", "Responsable", "Enseignants"]
|
|
if with_evals:
|
|
columns_ids += [
|
|
"date_evaluation",
|
|
"description",
|
|
"coefficient",
|
|
"evalcomplete_str",
|
|
"publish_incomplete_str",
|
|
]
|
|
|
|
titles = {title: title for title in columns_ids}
|
|
titles.update({f"ue_{ue.id}": ue.acronyme for ue in ues})
|
|
titles["ects"] = "ECTS"
|
|
titles["date_evaluation"] = "Évaluation"
|
|
titles["description"] = ""
|
|
titles["coefficient"] = "Coef. éval."
|
|
titles["evalcomplete_str"] = "Complète"
|
|
titles["parcours"] = "Parcours"
|
|
titles["publish_incomplete_str"] = "Toujours utilisée"
|
|
title = f"{parcours.SESSION_NAME.capitalize()} {formsemestre.titre_mois()}"
|
|
|
|
rows = []
|
|
sum_coef = 0
|
|
sum_ects = 0
|
|
last_ue_id = None
|
|
formsemestre_parcours_ids = {p.id for p in formsemestre.parcours}
|
|
for modimpl in formsemestre.modimpls_sorted:
|
|
# Ligne UE avec ECTS:
|
|
ue = modimpl.module.ue
|
|
if ue.id != last_ue_id:
|
|
last_ue_id = ue.id
|
|
if ue.ects is None:
|
|
ects_str = "-"
|
|
else:
|
|
sum_ects += ue.ects
|
|
ects_str = ue.ects
|
|
ue_info = {
|
|
"UE": ue.acronyme,
|
|
"Code": "",
|
|
"ects": ects_str,
|
|
"Module": ue.titre,
|
|
"_css_row_class": "table_row_ue",
|
|
}
|
|
if use_ue_coefs and ue.type != UE_SPORT:
|
|
ue_info["Coef."] = ue.coefficient or "0."
|
|
ue_info["_Coef._class"] = "ue_coef"
|
|
if not ue.coefficient:
|
|
ue_info["_Coef._class"] += " ue_coef_nul"
|
|
if ue.color:
|
|
for k in list(ue_info.keys()):
|
|
if not k.startswith("_"):
|
|
ue_info[f"_{k}_td_attrs"] = (
|
|
f'style="background-color: {ue.color} !important;"'
|
|
)
|
|
if not is_apc:
|
|
# n'affiche la ligne UE qu'en formation classique
|
|
# car l'UE de rattachement n'a pas d'intérêt en BUT
|
|
rows.append(ue_info)
|
|
|
|
mod_nb_inscrits = nt.modimpls_results[modimpl.id].nb_inscrits_module
|
|
enseignants = ", ".join(ens.get_prenomnom() for ens in modimpl.enseignants)
|
|
|
|
row = {
|
|
"UE": modimpl.module.ue.acronyme,
|
|
"_UE_td_attrs": ue_info.get("_UE_td_attrs", ""),
|
|
"Code": modimpl.module.code or "",
|
|
"Module": modimpl.module.abbrev or modimpl.module.titre,
|
|
"_Module_class": "scotext",
|
|
"Inscrits": mod_nb_inscrits,
|
|
"Responsable": sco_users.user_info(modimpl.responsable_id)["nomprenom"],
|
|
"_Responsable_class": "scotext",
|
|
"Enseignants": enseignants,
|
|
"_Enseignants_class": "scotext",
|
|
"Coef.": modimpl.module.coefficient,
|
|
# 'ECTS' : M['module']['ects'],
|
|
# Lien sur titre -> module
|
|
"_Module_target": url_for(
|
|
"notes.moduleimpl_status",
|
|
scodoc_dept=g.scodoc_dept,
|
|
moduleimpl_id=modimpl.id,
|
|
),
|
|
"_Code_target": url_for(
|
|
"notes.moduleimpl_status",
|
|
scodoc_dept=g.scodoc_dept,
|
|
moduleimpl_id=modimpl.id,
|
|
),
|
|
}
|
|
if modimpl.module.coefficient is not None:
|
|
sum_coef += modimpl.module.coefficient
|
|
coef_dict = modimpl.module.get_ue_coef_dict()
|
|
for ue in ues:
|
|
row[f"ue_{ue.id}"] = coef_dict.get(ue.id, 0.0) or ""
|
|
if with_parcours:
|
|
# Intersection des parcours du module avec ceux du formsemestre
|
|
row["parcours"] = ", ".join(
|
|
[
|
|
pa.code
|
|
for pa in (
|
|
modimpl.module.parcours
|
|
if modimpl.module.parcours
|
|
else modimpl.formsemestre.parcours
|
|
)
|
|
if pa.id in formsemestre_parcours_ids
|
|
]
|
|
)
|
|
|
|
rows.append(row)
|
|
|
|
if with_evals:
|
|
# Ajoute lignes pour evaluations
|
|
evals = nt.get_mod_evaluation_etat_list(modimpl)
|
|
evals.reverse() # ordre chronologique
|
|
# Ajoute etat:
|
|
eval_rows = []
|
|
for eval_dict in evals:
|
|
e = eval_dict.copy()
|
|
e["_description_target"] = url_for(
|
|
"notes.evaluation_listenotes",
|
|
scodoc_dept=g.scodoc_dept,
|
|
evaluation_id=e["evaluation_id"],
|
|
)
|
|
e["_date_evaluation_order"] = e["jour"].isoformat()
|
|
e["date_evaluation"] = (
|
|
e["jour"].strftime("%d/%m/%Y") if e["jour"] else ""
|
|
)
|
|
e["UE"] = row["UE"]
|
|
e["_UE_td_attrs"] = row["_UE_td_attrs"]
|
|
e["Code"] = row["Code"]
|
|
e["_css_row_class"] = "evaluation"
|
|
e["Module"] = "éval."
|
|
# Cosmetic: conversions pour affichage
|
|
if e["etat"]["evalcomplete"]:
|
|
e["evalcomplete_str"] = "Oui"
|
|
e["_evalcomplete_str_td_attrs"] = 'style="color: green;"'
|
|
else:
|
|
e["evalcomplete_str"] = "Non"
|
|
e["_evalcomplete_str_td_attrs"] = 'style="color: red;"'
|
|
|
|
if e["publish_incomplete"]:
|
|
e["publish_incomplete_str"] = "Oui"
|
|
e["_publish_incomplete_str_td_attrs"] = 'style="color: green;"'
|
|
else:
|
|
e["publish_incomplete_str"] = "Non"
|
|
e["_publish_incomplete_str_td_attrs"] = 'style="color: red;"'
|
|
# Poids vers UEs (en APC)
|
|
evaluation: Evaluation = db.session.get(Evaluation, e["evaluation_id"])
|
|
for ue_id, poids in evaluation.get_ue_poids_dict().items():
|
|
e[f"ue_{ue_id}"] = poids or ""
|
|
e[f"_ue_{ue_id}_class"] = "poids"
|
|
e[f"_ue_{ue_id}_help"] = "poids vers l'UE"
|
|
eval_rows.append(e)
|
|
|
|
rows += eval_rows
|
|
|
|
sums = {"_css_row_class": "moyenne sortbottom", "ects": sum_ects, "Coef.": sum_coef}
|
|
rows.append(sums)
|
|
|
|
return GenTable(
|
|
columns_ids=columns_ids,
|
|
rows=rows,
|
|
titles=titles,
|
|
origin=f"Généré par {sco_version.SCONAME} le {scu.timedate_human_repr()}",
|
|
caption=title,
|
|
html_caption=title,
|
|
html_class="table_leftalign formsemestre_description",
|
|
base_url=f"{request.base_url}?formsemestre_id={formsemestre_id}&with_evals={with_evals}",
|
|
page_title=title,
|
|
html_title=html_sco_header.html_sem_header(
|
|
"Description du semestre", with_page_header=False
|
|
),
|
|
pdf_title=title,
|
|
preferences=sco_preferences.SemPreferences(formsemestre_id),
|
|
)
|
|
|
|
|
|
def formsemestre_description(
|
|
formsemestre_id, fmt="html", with_evals=False, with_parcours=False
|
|
):
|
|
"""Description du semestre sous forme de table exportable
|
|
Liste des modules et de leurs coefficients
|
|
"""
|
|
with_evals = int(with_evals)
|
|
tab = formsemestre_description_table(
|
|
formsemestre_id, with_evals=with_evals, with_parcours=with_parcours
|
|
)
|
|
tab.html_before_table = f"""
|
|
<form name="f" method="get" action="{request.base_url}">
|
|
<input type="hidden" name="formsemestre_id" value="{formsemestre_id}"></input>
|
|
<input type="checkbox" name="with_evals" value="1" onchange="document.f.submit()"
|
|
{ "checked" if with_evals else "" }
|
|
>indiquer les évaluations</input>
|
|
<input type="checkbox" name="with_parcours" value="1" onchange="document.f.submit()"
|
|
{ "checked" if with_parcours else "" }
|
|
>indiquer les parcours BUT</input>
|
|
"""
|
|
|
|
return tab.make_page(fmt=fmt)
|
|
|
|
|
|
# genere liste html pour accès aux groupes de ce semestre
|
|
def _make_listes_sem(formsemestre: FormSemestre) -> str:
|
|
"""La section avec les groupes et l'assiduité"""
|
|
H = []
|
|
# pas de menu absences si pas autorise:
|
|
can_edit_abs = current_user.has_permission(Permission.AbsChange)
|
|
#
|
|
H.append(
|
|
f"""<h3>Groupes et absences de {formsemestre.titre}
|
|
<span class="infostitresem">({
|
|
formsemestre.mois_debut()} - {formsemestre.mois_fin()
|
|
})</span></h3>"""
|
|
)
|
|
#
|
|
H.append('<div class="sem-groups-abs">')
|
|
# Genere liste pour chaque partition (categorie de groupes)
|
|
for partition in formsemestre.get_partitions_list():
|
|
groups = partition.groups.all()
|
|
effectifs = {g.id: g.get_nb_inscrits() for g in groups}
|
|
partition_is_empty = sum(effectifs.values()) == 0
|
|
H.append(
|
|
f"""
|
|
<div class="sem-groups-partition">
|
|
<div class="sem-groups-partition-titre">{
|
|
'Groupes de ' + partition.partition_name
|
|
if partition.partition_name else
|
|
'Tous les étudiants'}
|
|
</div>
|
|
<div class="sem-groups-partition-titre">{
|
|
"Gestion de l'assiduité" if not partition_is_empty else ""
|
|
}</div>
|
|
"""
|
|
)
|
|
if groups:
|
|
for group in groups:
|
|
n_members = effectifs[group.id]
|
|
if n_members == 0:
|
|
continue # skip empty groups
|
|
partition_is_empty = False
|
|
group_label = f"{group.group_name}" if group.group_name else "liste"
|
|
H.append(
|
|
f"""
|
|
<div class="sem-groups-list">
|
|
<div>
|
|
<a class="stdlink" href="{
|
|
url_for("scolar.groups_view",
|
|
group_ids=group.id,
|
|
scodoc_dept=g.scodoc_dept,
|
|
)
|
|
}">{group_label}
|
|
- {n_members} étudiants</a>
|
|
</div>
|
|
</div>
|
|
<div class="sem-groups-assi">
|
|
<div>
|
|
<a class="btn" href="{
|
|
url_for("assiduites.visu_assi_group",
|
|
scodoc_dept=g.scodoc_dept,
|
|
date_debut=formsemestre.date_debut.isoformat(),
|
|
date_fin=formsemestre.date_fin.isoformat(),
|
|
group_ids=group.id,
|
|
)}">
|
|
<button>Bilan assiduité</button></a>
|
|
</div>
|
|
"""
|
|
)
|
|
if can_edit_abs:
|
|
H.append(
|
|
f"""
|
|
<div>
|
|
<a class="btn" href="{
|
|
url_for("assiduites.visu_assiduites_group",
|
|
scodoc_dept=g.scodoc_dept,
|
|
formsemestre_id=formsemestre.id,
|
|
jour = datetime.date.today().isoformat(),
|
|
group_ids=group.id,
|
|
)}">
|
|
<button>Visualiser</button></a>
|
|
</div>
|
|
<div>
|
|
<a class="btn" href="{
|
|
url_for("assiduites.signal_assiduites_group",
|
|
scodoc_dept=g.scodoc_dept,
|
|
jour=datetime.date.today().isoformat(),
|
|
formsemestre_id=formsemestre.id,
|
|
group_ids=group.id,
|
|
)}">
|
|
<button>Saisie journalière</button></a>
|
|
</div>
|
|
<div>
|
|
<a class="btn" href="{
|
|
url_for("assiduites.signal_assiduites_diff",
|
|
scodoc_dept=g.scodoc_dept,
|
|
formsemestre_id=formsemestre.id,
|
|
group_ids=group.id,
|
|
)}">
|
|
<button>Saisie différée</button></a>
|
|
</div>
|
|
<div>
|
|
<a class="btn" href="{
|
|
url_for("assiduites.bilan_dept",
|
|
scodoc_dept=g.scodoc_dept,
|
|
formsemestre_id=formsemestre.id,
|
|
group_ids=group.id,
|
|
)}">
|
|
<button>Justificatifs en attente</button></a>
|
|
</div>
|
|
"""
|
|
)
|
|
|
|
H.append("</div>") # /sem-groups-assi
|
|
if partition_is_empty:
|
|
H.append(
|
|
'<div class="help sem-groups-none">Aucun groupe peuplé dans cette partition'
|
|
)
|
|
if formsemestre.can_change_groups():
|
|
H.append(
|
|
f""" (<a href="{url_for("scolar.partition_editor",
|
|
scodoc_dept=g.scodoc_dept,
|
|
formsemestre_id=formsemestre.id,
|
|
edit_partition=1)
|
|
}" class="stdlink">créer</a>)"""
|
|
)
|
|
H.append("</div>")
|
|
H.append("</div>") # /sem-groups-partition
|
|
|
|
if formsemestre.can_change_groups():
|
|
H.append(
|
|
f"""<h4><a class="stdlink"
|
|
href="{url_for("scolar.partition_editor",
|
|
scodoc_dept=g.scodoc_dept,
|
|
formsemestre_id=formsemestre.id,
|
|
edit_partition=1)
|
|
}">Ajouter une partition</a></h4>"""
|
|
)
|
|
|
|
H.append("</div>")
|
|
return "\n".join(H)
|
|
|
|
|
|
def formsemestre_status_head(formsemestre_id: int = None, page_title: str = None):
|
|
"""En-tête HTML des pages "semestre" """
|
|
formsemestre: FormSemestre = db.session.get(FormSemestre, formsemestre_id)
|
|
if not formsemestre:
|
|
raise ScoValueError("Semestre inexistant (il a peut être été supprimé ?)")
|
|
formation: Formation = formsemestre.formation
|
|
parcours = formation.get_cursus()
|
|
|
|
page_title = page_title or "Modules de "
|
|
|
|
H = [
|
|
html_sco_header.html_sem_header(
|
|
page_title, with_page_header=False, with_h2=False
|
|
),
|
|
f"""<table>
|
|
<tr><td class="fichetitre2">Formation: </td><td>
|
|
<a href="{url_for('notes.ue_table',
|
|
scodoc_dept=g.scodoc_dept, formation_id=formsemestre.formation.id)}"
|
|
class="discretelink" title="Formation {
|
|
formation.acronyme}, v{formation.version}">{formation.titre}</a>
|
|
""",
|
|
]
|
|
if formsemestre.semestre_id >= 0:
|
|
H.append(f", {parcours.SESSION_NAME} {formsemestre.semestre_id}")
|
|
if formsemestre.modalite:
|
|
H.append(f" en {formsemestre.modalite}")
|
|
if formsemestre.etapes:
|
|
H.append(
|
|
f""" (étape <b><tt>{
|
|
formsemestre.etapes_apo_str() or "-"
|
|
}</tt></b>)"""
|
|
)
|
|
H.append("</td></tr>")
|
|
if formation.is_apc():
|
|
# Affiche les parcours BUT cochés. Si aucun, tous ceux du référentiel.
|
|
sem_parcours = formsemestre.get_parcours_apc()
|
|
H.append(
|
|
f"""
|
|
<tr><td class="fichetitre2">Parcours: </td>
|
|
<td style="color: blue;">{', '.join(parcours.code for parcours in sem_parcours)}</td>
|
|
</tr>
|
|
"""
|
|
)
|
|
|
|
evals = sco_evaluations.do_evaluation_etat_in_sem(formsemestre)
|
|
H.append(
|
|
'<tr><td class="fichetitre2">Évaluations: </td><td> %(nb_evals_completes)s ok, %(nb_evals_en_cours)s en cours, %(nb_evals_vides)s vides'
|
|
% evals
|
|
)
|
|
if evals["last_modif"]:
|
|
H.append(
|
|
" <em>(dernière note saisie le %s)</em>"
|
|
% evals["last_modif"].strftime("%d/%m/%Y à %Hh%M")
|
|
)
|
|
H.append("</td></tr>")
|
|
H.append("</table>")
|
|
warnings = []
|
|
if evals["attente"]:
|
|
warnings.append(
|
|
"""<span class="fontred">Il y a des notes en attente !</span>
|
|
Le classement des étudiants n'a qu'une valeur indicative."""
|
|
)
|
|
if formsemestre.bul_hide_xml:
|
|
warnings.append("""Bulletins non publiés sur la passerelle.""")
|
|
if formsemestre.block_moyennes:
|
|
warnings.append("Calcul des moyennes bloqué !")
|
|
if formsemestre.semestre_id >= 0 and not formsemestre.est_sur_une_annee():
|
|
warnings.append("""<em>Ce semestre couvre plusieurs années scolaires !</em>""")
|
|
if warnings:
|
|
H += [
|
|
f"""<div class="formsemestre_status_warning">{warning}</div>"""
|
|
for warning in warnings
|
|
]
|
|
|
|
return "".join(H)
|
|
|
|
|
|
def formsemestre_status(formsemestre_id=None, check_parcours=True):
|
|
"""Tableau de bord semestre HTML"""
|
|
# porté du DTML
|
|
if formsemestre_id is not None and not isinstance(formsemestre_id, int):
|
|
raise ScoInvalidIdType(
|
|
"formsemestre_bulletinetud: formsemestre_id must be an integer !"
|
|
)
|
|
formsemestre: FormSemestre = FormSemestre.query.get_or_404(formsemestre_id)
|
|
# S'assure que les groupes de parcours sont à jour:
|
|
if int(check_parcours):
|
|
formsemestre.setup_parcours_groups()
|
|
modimpls = formsemestre.modimpls_sorted
|
|
nt = res_sem.load_formsemestre_results(formsemestre)
|
|
|
|
# Construit la liste de tous les enseignants de ce semestre:
|
|
mails_enseignants = set(u.email for u in formsemestre.responsables)
|
|
for modimpl in modimpls:
|
|
mails_enseignants.add(sco_users.user_info(modimpl.responsable_id)["email"])
|
|
mails_enseignants |= {u.email for u in modimpl.enseignants if u.email}
|
|
|
|
can_edit = formsemestre.can_be_edited_by(current_user)
|
|
can_change_all_notes = current_user.has_permission(Permission.EditAllNotes) or (
|
|
current_user.id in [resp.id for resp in formsemestre.responsables]
|
|
)
|
|
use_ue_coefs = sco_preferences.get_preference("use_ue_coefs", formsemestre_id)
|
|
|
|
H = [
|
|
html_sco_header.sco_header(
|
|
page_title=f"{formsemestre.sem_modalite()} {formsemestre.titre_annee()}"
|
|
),
|
|
'<div class="formsemestre_status">',
|
|
formsemestre_status_head(
|
|
formsemestre_id=formsemestre_id, page_title="Tableau de bord"
|
|
),
|
|
formsemestre_warning_apc_setup(formsemestre, nt),
|
|
(
|
|
formsemestre_warning_etuds_sans_note(formsemestre, nt)
|
|
if can_change_all_notes
|
|
else ""
|
|
),
|
|
"""<p style="font-size: 130%"><b>Tableau de bord : </b>""",
|
|
]
|
|
if formsemestre.est_courant():
|
|
H.append(
|
|
"""<span class="help">cliquez sur un module pour saisir des notes</span>"""
|
|
)
|
|
elif datetime.date.today() > formsemestre.date_fin:
|
|
if formsemestre.etat:
|
|
H.append(
|
|
"""<span
|
|
class="formsemestre_status_warning">semestre terminé mais non verrouillé</span>"""
|
|
)
|
|
else:
|
|
H.append(
|
|
"""<span class="formsemestre_status_warning">semestre pas encore commencé</span>"""
|
|
)
|
|
H.append("</p>")
|
|
|
|
if sco_preferences.get_preference("bul_show_all_evals", formsemestre_id):
|
|
H.append(
|
|
"""<div class="formsemestre_status_warning"
|
|
>Toutes évaluations (même incomplètes) visibles</div>"""
|
|
)
|
|
|
|
if nt.parcours.APC_SAE:
|
|
# BUT: tableau ressources puis SAE
|
|
ressources = [
|
|
m for m in modimpls if m.module.module_type == ModuleType.RESSOURCE
|
|
]
|
|
saes = [m for m in modimpls if m.module.module_type == ModuleType.SAE]
|
|
autres = [
|
|
m
|
|
for m in modimpls
|
|
if m.module.module_type not in (ModuleType.RESSOURCE, ModuleType.SAE)
|
|
]
|
|
H += [
|
|
f"""
|
|
<div class="tableau_modules">
|
|
{_TABLEAU_MODULES_HEAD}
|
|
<tr class="formsemestre_status_cat">
|
|
<td colspan="5">
|
|
<span class="status_module_cat">Ressources</span>
|
|
</td>
|
|
</tr>
|
|
{formsemestre_tableau_modules(
|
|
ressources, nt, formsemestre, can_edit=can_edit, show_ues=False
|
|
)}
|
|
<tr class="formsemestre_status_cat">
|
|
<td colspan="5">
|
|
<span class="status_module_cat">SAÉs</span>
|
|
</td>
|
|
</tr>""",
|
|
formsemestre_tableau_modules(
|
|
saes, nt, formsemestre, can_edit=can_edit, show_ues=False
|
|
),
|
|
]
|
|
if autres:
|
|
H += [
|
|
"""<tr class="formsemestre_status_cat">
|
|
<td colspan="5">
|
|
<span class="status_module_cat">Autres modules</span>
|
|
</td></tr>""",
|
|
formsemestre_tableau_modules(
|
|
autres, nt, formsemestre, can_edit=can_edit, show_ues=False
|
|
),
|
|
]
|
|
H += [_TABLEAU_MODULES_FOOT, "</div>"]
|
|
else:
|
|
# formations classiques: groupe par UE
|
|
# élimine les modules BUT qui aurait pu se glisser là suite à un
|
|
# changement de type de formation par exemple
|
|
modimpls_classic = [
|
|
m
|
|
for m in modimpls
|
|
if m.module.module_type not in (ModuleType.RESSOURCE, ModuleType.SAE)
|
|
]
|
|
H += [
|
|
"<p>",
|
|
_TABLEAU_MODULES_HEAD,
|
|
formsemestre_tableau_modules(
|
|
modimpls_classic,
|
|
nt,
|
|
formsemestre,
|
|
can_edit=can_edit,
|
|
use_ue_coefs=use_ue_coefs,
|
|
),
|
|
_TABLEAU_MODULES_FOOT,
|
|
"</p>",
|
|
]
|
|
|
|
if use_ue_coefs and not formsemestre.formation.is_apc():
|
|
H.append(
|
|
"""
|
|
<p class="infop">utilise les coefficients d'UE pour calculer la moyenne générale.</p>
|
|
"""
|
|
)
|
|
# --- LISTE DES ETUDIANTS
|
|
H += [
|
|
'<div class="formsemestre-groupes">',
|
|
_make_listes_sem(formsemestre),
|
|
"</div>",
|
|
]
|
|
# --- Lien mail enseignants:
|
|
adrlist = list(mails_enseignants - {None, ""})
|
|
if adrlist:
|
|
H.append(
|
|
f"""<p>
|
|
<a class="stdlink" href="mailto:?cc={','.join(adrlist)}">Courrier aux {
|
|
len(adrlist)} enseignants du semestre</a>
|
|
</p>"""
|
|
)
|
|
return "".join(H) + html_sco_header.sco_footer()
|
|
|
|
|
|
_TABLEAU_MODULES_HEAD = """
|
|
<table class="formsemestre_status">
|
|
<tr>
|
|
<th class="formsemestre_status">Code</th>
|
|
<th class="formsemestre_status">Module</th>
|
|
<th class="formsemestre_status">Inscrits</th>
|
|
<th class="resp">Responsable</th>
|
|
<th class="coef">Coefs.</th>
|
|
<th class="evals">Évaluations</th>
|
|
</tr>
|
|
"""
|
|
_TABLEAU_MODULES_FOOT = """</table>"""
|
|
|
|
|
|
def formsemestre_tableau_modules(
|
|
modimpls: list[ModuleImpl],
|
|
nt,
|
|
formsemestre: FormSemestre,
|
|
can_edit=True,
|
|
show_ues=True,
|
|
use_ue_coefs=False,
|
|
) -> str:
|
|
"Lignes table HTML avec modules du semestre"
|
|
H = []
|
|
prev_ue_id = None
|
|
for modimpl in modimpls:
|
|
mod: Module = modimpl.module
|
|
moduleimpl_status_url = url_for(
|
|
"notes.moduleimpl_status",
|
|
scodoc_dept=g.scodoc_dept,
|
|
moduleimpl_id=modimpl.id,
|
|
)
|
|
mod_descr = "Module " + (mod.titre or "")
|
|
is_apc = mod.is_apc() # SAE ou ressource
|
|
if is_apc:
|
|
coef_descr = ", ".join(
|
|
[
|
|
f"{ue.acronyme}: {co}"
|
|
for ue, co in mod.ue_coefs_list()
|
|
if isinstance(co, float) and co > 0
|
|
]
|
|
)
|
|
if coef_descr:
|
|
mod_descr += " Coefs: " + coef_descr
|
|
else:
|
|
mod_descr += " (pas de coefficients) "
|
|
else:
|
|
mod_descr += ", coef. " + str(mod.coefficient)
|
|
mod_ens = sco_users.user_info(modimpl.responsable_id)["nomcomplet"]
|
|
if modimpl.enseignants.count():
|
|
mod_ens += " (resp.), " + ", ".join(
|
|
[u.get_nomcomplet() for u in modimpl.enseignants]
|
|
)
|
|
mod_nb_inscrits = nt.modimpls_results[modimpl.id].nb_inscrits_module
|
|
mod_is_conforme = modimpl.check_apc_conformity(nt)
|
|
ue = modimpl.module.ue
|
|
if show_ues and (prev_ue_id != ue.id):
|
|
prev_ue_id = ue.id
|
|
titre = ue.titre or ""
|
|
if use_ue_coefs:
|
|
titre += f""" <b>(coef. {ue.coefficient or 0.0})</b>"""
|
|
H.append(
|
|
f"""<tr class="formsemestre_status_ue">
|
|
<td colspan="4">
|
|
<span class="status_ue_acro">{ue.acronyme}</span>
|
|
<span class="status_ue_title">{titre}</span>
|
|
</td>
|
|
<td colspan="2">"""
|
|
)
|
|
|
|
expr = sco_compute_moy.get_ue_expression(
|
|
formsemestre.id, ue.id, html_quote=True
|
|
)
|
|
if expr:
|
|
H.append(
|
|
f""" <span class="formula" title="mode de calcul de la moyenne d'UE">{expr}</span>
|
|
<span class="warning">formule inutilisée en ScoDoc 9: <a href="{
|
|
url_for("notes.delete_ue_expr", scodoc_dept=g.scodoc_dept, formsemestre_id=formsemestre.id, ue_id=ue.id )
|
|
}
|
|
">supprimer</a></span>"""
|
|
)
|
|
|
|
H.append("</td></tr>")
|
|
|
|
if ue.type != codes_cursus.UE_STANDARD:
|
|
fontorange = " fontorange" # style css additionnel
|
|
else:
|
|
fontorange = ""
|
|
|
|
etat = sco_evaluations.do_evaluation_etat_in_mod(nt, modimpl)
|
|
if (
|
|
etat["nb_evals_completes"] > 0
|
|
and etat["nb_evals_en_cours"] == 0
|
|
and etat["nb_evals_vides"] == 0
|
|
and not etat["attente"]
|
|
and not etat["nb_evals_blocked"] > 0
|
|
):
|
|
tr_classes = f"formsemestre_status_green{fontorange}"
|
|
else:
|
|
tr_classes = f"formsemestre_status{fontorange}"
|
|
if etat["attente"]:
|
|
tr_classes += " modimpl_attente"
|
|
if not mod_is_conforme:
|
|
tr_classes += " modimpl_non_conforme"
|
|
if etat["nb_evals_blocked"] > 0:
|
|
tr_classes += " modimpl_has_blocked"
|
|
H.append(
|
|
f"""
|
|
<tr class="{tr_classes}">
|
|
<td class="formsemestre_status_code"><a
|
|
href="{moduleimpl_status_url}"
|
|
title="{mod_descr}" class="stdlink">{mod.code}</a></td>
|
|
<td class="scotext"><a href="{moduleimpl_status_url}" title="{mod_descr}"
|
|
class="formsemestre_status_link">{mod.abbrev or mod.titre or ""}</a>
|
|
</td>
|
|
<td class="formsemestre_status_inscrits">{mod_nb_inscrits}</td>
|
|
<td class="resp scotext">
|
|
<a class="discretelink" href="{moduleimpl_status_url}" title="{mod_ens}">{
|
|
sco_users.user_info(modimpl.responsable_id)["prenomnom"]
|
|
}</a>
|
|
</td>
|
|
<td>
|
|
"""
|
|
)
|
|
if mod.module_type in (ModuleType.RESSOURCE, ModuleType.SAE):
|
|
coefs = mod.ue_coefs_list(ues=formsemestre.get_ues())
|
|
H.append(f'<a class="invisible_link" href="#" title="{mod_descr}">')
|
|
for coef in coefs:
|
|
if coef[1] > 0:
|
|
H.append(
|
|
f"""<span class="mod_coef_indicator"
|
|
title="{coef[0].acronyme}"
|
|
style="background: {
|
|
coef[0].color if coef[0].color is not None else 'blue'
|
|
}"></span>"""
|
|
)
|
|
else:
|
|
H.append("""<span class="mod_coef_indicator_zero"></span>""")
|
|
H.append("</a>")
|
|
H.append("</td>")
|
|
if mod.module_type in (
|
|
None, # ne devrait pas être nécessaire car la migration a remplacé les NULLs
|
|
ModuleType.STANDARD,
|
|
ModuleType.RESSOURCE,
|
|
ModuleType.SAE,
|
|
):
|
|
H.append('<td class="evals">')
|
|
nb_evals = etat["nb_evals"]
|
|
if nb_evals != 0:
|
|
if etat["nb_evals_blocked"] > 0:
|
|
blocked_txt = f"""<span class="nb_evals_blocked">{
|
|
etat["nb_evals_blocked"]} bloquée{'s'
|
|
if etat["nb_evals_blocked"] > 1 else ''}</span>"""
|
|
else:
|
|
blocked_txt = ""
|
|
H.append(
|
|
f"""<a href="{moduleimpl_status_url}"
|
|
title="les évaluations 'ok' sont celles prises en compte dans les calculs"
|
|
class="formsemestre_status_link">{nb_evals} prévues,
|
|
{etat["nb_evals_completes"]} ok {blocked_txt}
|
|
</a>"""
|
|
)
|
|
if etat["nb_evals_en_cours"] > 0:
|
|
H.append(
|
|
f""", <span><a class="redlink" href="{moduleimpl_status_url}"
|
|
title="Il manque des notes">{
|
|
etat["nb_evals_en_cours"]
|
|
} en cours</a></span>"""
|
|
)
|
|
if etat["attente"]:
|
|
H.append(
|
|
f""" <span><a class="redlink" href="{moduleimpl_status_url}"
|
|
title="Il y a des notes en attente"><span class="evals_attente">en attente</span></a></span>"""
|
|
)
|
|
if not mod_is_conforme:
|
|
H.append(
|
|
f""" <span><a class="redlink" href="{moduleimpl_status_url}"
|
|
title="évaluations non conformes">[non conforme]</a></span>"""
|
|
)
|
|
elif mod.module_type == ModuleType.MALUS:
|
|
nb_malus_notes = sum(
|
|
e["etat"]["nb_notes"] for e in nt.get_mod_evaluation_etat_list(modimpl)
|
|
)
|
|
H.append(
|
|
f"""<td class="malus">
|
|
<a href="{moduleimpl_status_url}" class="formsemestre_status_link">malus
|
|
({nb_malus_notes} notes)</a>
|
|
"""
|
|
)
|
|
else:
|
|
raise ValueError(f"Invalid module_type {mod.module_type}") # a bug
|
|
|
|
H.append("</td></tr>")
|
|
return "\n".join(H)
|
|
|
|
|
|
# Expérimental
|
|
def get_formsemestre_etudids_sans_notes(
|
|
formsemestre: FormSemestre, res: ResultatsSemestre
|
|
) -> set[int]:
|
|
"""Les étudids d'étudiants de ce semestre n'ayant aucune note
|
|
alors que d'autres en ont.
|
|
"""
|
|
# Il y a-t-il des notes déjà saisies ?
|
|
nb_notes_sem = (
|
|
NotesNotes.query.join(Evaluation)
|
|
.join(ModuleImpl)
|
|
.filter_by(formsemestre_id=formsemestre.id)
|
|
.count()
|
|
)
|
|
if not nb_notes_sem:
|
|
return set()
|
|
notes_modimpls = [
|
|
set.intersection(*m_res.evals_etudids_sans_note.values())
|
|
for m_res in res.modimpls_results.values()
|
|
if m_res.evals_etudids_sans_note
|
|
]
|
|
if not notes_modimpls:
|
|
return set()
|
|
etudids_sans_notes = set.intersection(*notes_modimpls)
|
|
nb_sans_notes = len(etudids_sans_notes)
|
|
if nb_sans_notes > 0 and nb_sans_notes < len(
|
|
formsemestre.get_inscrits(include_demdef=False)
|
|
):
|
|
return etudids_sans_notes
|
|
return set()
|
|
|
|
|
|
def formsemestre_warning_etuds_sans_note(
|
|
formsemestre: FormSemestre, res: ResultatsSemestre
|
|
) -> str:
|
|
"""Vérifie si on est dans la situation où certains (mais pas tous) étudiants
|
|
n'ont aucune note alors que d'autres en ont.
|
|
Ce cas se produit typiquement quand on inscrit un étudiant en cours de semestre.
|
|
Il est alors utile de proposer de mettre toutes ses notes à ABS, ATT ou EXC
|
|
pour éviter de laisser toutes les évaluations "incomplètes".
|
|
"""
|
|
etudids_sans_notes = get_formsemestre_etudids_sans_notes(formsemestre, res)
|
|
if not etudids_sans_notes:
|
|
return ""
|
|
nb_sans_notes = len(etudids_sans_notes)
|
|
if nb_sans_notes < 5:
|
|
# peu d'étudiants, affiche leurs noms
|
|
etuds: list[Identite] = sorted(
|
|
[Identite.get_etud(etudid) for etudid in etudids_sans_notes],
|
|
key=lambda e: e.sort_key,
|
|
)
|
|
noms = ", ".join(
|
|
[
|
|
f"""<a href="{
|
|
url_for("scolar.fiche_etud", scodoc_dept=g.scodoc_dept, etudid=etud.id)
|
|
}" class="discretelink">{etud.nomprenom}</a>"""
|
|
for etud in etuds
|
|
]
|
|
)
|
|
msg_etuds = (
|
|
f"""{noms} n'{"a" if nb_sans_notes == 1 else "ont"} aucune note :"""
|
|
)
|
|
else:
|
|
msg_etuds = f"""{nb_sans_notes} étudiants n'ont aucune note :"""
|
|
|
|
return f"""<div class="formsemestre_status_warning">{msg_etuds}
|
|
<a class="stdlink" href="{url_for(
|
|
"notes.formsemestre_note_etuds_sans_notes",
|
|
scodoc_dept=g.scodoc_dept,
|
|
formsemestre_id=formsemestre.id,
|
|
)}">{"lui" if nb_sans_notes == 1 else "leur"}
|
|
<span title="pour ne pas bloquer les autres étudiants, il est souvent préférable
|
|
que les nouveaux aient des notes provisoires">affecter des notes</a>.
|
|
</div>
|
|
"""
|
|
|
|
|
|
def formsemestre_note_etuds_sans_notes(
|
|
formsemestre_id: int, code: str = None, etudid: int = None
|
|
):
|
|
"""Affichage et saisie des étudiants sans notes
|
|
|
|
Si etudid est spécifié, traite un seul étudiant."""
|
|
formsemestre: FormSemestre = FormSemestre.query.filter_by(
|
|
id=formsemestre_id, dept_id=g.scodoc_dept_id
|
|
).first_or_404()
|
|
res: ResultatsSemestre = res_sem.load_formsemestre_results(formsemestre)
|
|
etudids_sans_notes = get_formsemestre_etudids_sans_notes(formsemestre, res)
|
|
if etudid:
|
|
etudids_sans_notes = etudids_sans_notes.intersection({etudid})
|
|
etuds: list[Identite] = sorted(
|
|
[Identite.get_etud(eid) for eid in etudids_sans_notes],
|
|
key=lambda e: e.sort_key,
|
|
)
|
|
if request.method == "POST":
|
|
if not code in ("ATT", "EXC", "ABS"):
|
|
raise ScoValueError("code invalide: doit être ATT, ABS ou EXC")
|
|
for etud in etuds:
|
|
formsemestre.etud_set_all_missing_notes(etud, code)
|
|
flash(f"Notes de {len(etuds)} étudiants affectées à {code}")
|
|
return redirect(
|
|
url_for(
|
|
"notes.formsemestre_status",
|
|
scodoc_dept=g.scodoc_dept,
|
|
formsemestre_id=formsemestre.id,
|
|
)
|
|
)
|
|
if not etuds:
|
|
if etudid is None:
|
|
message = """<h3>aucun étudiant sans notes</h3>"""
|
|
else:
|
|
flash(
|
|
f"""{Identite.get_etud(etudid).nomprenom}
|
|
a déjà des notes"""
|
|
)
|
|
return redirect(
|
|
url_for("scolar.fiche_etud", scodoc_dept=g.scodoc_dept, etudid=etudid)
|
|
)
|
|
else:
|
|
noms = "</li><li>".join(
|
|
[
|
|
f"""<a href="{
|
|
url_for("scolar.fiche_etud", scodoc_dept=g.scodoc_dept, etudid=etud.id)
|
|
}" class="discretelink">{etud.nomprenom}</a>"""
|
|
for etud in etuds
|
|
]
|
|
)
|
|
message = f"""
|
|
<h3>Étudiants sans notes:</h3>
|
|
<ul>
|
|
<li>{noms}</li>
|
|
</ul>
|
|
"""
|
|
|
|
return f"""
|
|
{html_sco_header.sco_header(
|
|
page_title=f"{formsemestre.sem_modalite()} {formsemestre.titre_annee()}"
|
|
)}
|
|
<div class="formsemestre_status">
|
|
{formsemestre_status_head(
|
|
formsemestre_id=formsemestre_id, page_title="Étudiants sans notes"
|
|
)}
|
|
</div>
|
|
{message}
|
|
|
|
<form method="post">
|
|
<input type="hidden" name="formsemestre_id" value="{formsemestre.id}">
|
|
<input type="hidden" name="etudid" value="{etudid or ""}">
|
|
|
|
Mettre toutes les notes de {"ces étudiants" if len(etuds)> 1 else "cet étudiant"}
|
|
à :
|
|
<select name="code">
|
|
<option value="ABS">ABS (absent, compte zéro)</option>
|
|
<option value="ATT" selected>ATT (en attente)</option>
|
|
<option value="EXC">EXC (neutralisée)</option>
|
|
</select>
|
|
<input type="submit" name="enregistrer">
|
|
</form>
|
|
{html_sco_header.sco_footer()}
|
|
"""
|