Merge branch 'iziram-master'

This commit is contained in:
Emmanuel Viennet 2024-07-10 21:31:51 +02:00
commit 63a44de4b8
8 changed files with 178 additions and 27 deletions

View File

@ -21,6 +21,7 @@ from app.scodoc import sco_abs_notification
from app.scodoc.sco_archives_justificatifs import JustificatifArchiver from app.scodoc.sco_archives_justificatifs import JustificatifArchiver
from app.scodoc.sco_exceptions import ScoValueError from app.scodoc.sco_exceptions import ScoValueError
from app.scodoc.sco_permissions import Permission from app.scodoc.sco_permissions import Permission
from app.scodoc import sco_preferences
from app.scodoc import sco_utils as scu from app.scodoc import sco_utils as scu
from app.scodoc.sco_utils import ( from app.scodoc.sco_utils import (
EtatAssiduite, EtatAssiduite,
@ -188,6 +189,12 @@ class Assiduite(ScoDocModel):
): ):
raise ScoValueError("La date de fin n'est pas un jour travaillé") raise ScoValueError("La date de fin n'est pas un jour travaillé")
# Vérification de l'activation du module
if (err_msg := has_assiduites_disable_pref(formsemestre_date_debut)) or (
err_msg := has_assiduites_disable_pref(formsemestre_date_fin)
):
raise ScoValueError(err_msg)
# Vérification de non duplication des périodes # Vérification de non duplication des périodes
assiduites: Query = etud.assiduites assiduites: Query = etud.assiduites
if is_period_conflicting(date_debut, date_fin, assiduites, Assiduite): if is_period_conflicting(date_debut, date_fin, assiduites, Assiduite):
@ -817,3 +824,29 @@ def get_formsemestre_from_data(data: dict[str, datetime | int]) -> FormSemestre:
) )
.first() .first()
) )
def has_assiduites_disable_pref(formsemestre: FormSemestre) -> str | bool:
"""
Vérifie si le semestre possède la préférence "assiduites_disable"
et renvoie le message d'erreur associé.
La préférence est un text field. Il est considéré comme vide si :
- la chaine de caractère est vide
- si elle n'est composée que de caractères d'espacement (espace, tabulation, retour à la ligne)
Si la chaine est vide, la fonction renvoie False
"""
# Si pas de formsemestre, on ne peut pas vérifier la préférence
# On considère que la préférence n'est pas activée
if formsemestre is None:
return False
pref: str = (
sco_preferences.get_preference("assiduites_disable", formsemestre.id) or ""
)
pref = pref.strip()
return pref if pref else False

View File

@ -3,9 +3,10 @@ Ecrit par Matthias Hartmann.
""" """
from datetime import date, datetime, time, timedelta from datetime import date, datetime, time, timedelta
from functools import wraps
from pytz import UTC from pytz import UTC
from flask import g from flask import g, request
from flask_sqlalchemy.query import Query from flask_sqlalchemy.query import Query
from app import log, db, set_sco_dept from app import log, db, set_sco_dept
@ -18,12 +19,13 @@ from app.models import (
ModuleImplInscription, ModuleImplInscription,
ScoDocSiteConfig, ScoDocSiteConfig,
) )
from app.models.assiduites import Assiduite, Justificatif from app.models.assiduites import Assiduite, Justificatif, has_assiduites_disable_pref
from app.scodoc import sco_formsemestre_inscriptions from app.scodoc import sco_formsemestre_inscriptions
from app.scodoc import sco_preferences from app.scodoc import sco_preferences
from app.scodoc import sco_cache from app.scodoc import sco_cache
from app.scodoc import sco_etud from app.scodoc import sco_etud
import app.scodoc.sco_utils as scu import app.scodoc.sco_utils as scu
from app.scodoc.sco_exceptions import ScoValueError
class CountCalculator: class CountCalculator:
@ -819,6 +821,38 @@ def get_etud_evaluations_assiduites(etud: Identite) -> list[dict]:
return etud_evaluations_assiduites return etud_evaluations_assiduites
# --- Décorateur ---
def check_disabled(func):
"""
Vérifie sur le module a été désactivé dans les préférences du semestre.
Récupère le formsemestre depuis l'url (formsemestre_id)
Si le formsemestre est trouvé :
- Vérifie si le module a été désactivé dans les préférences du semestre
- Si le module a été désactivé, une ScoValueError est levée
Sinon :
Il ne se passe rien
"""
@wraps(func)
def decorated_function(*args, **kwargs):
# Récupération du formsemestre depuis l'url
formsemestre_id = request.args.get("formsemestre_id")
# Si on a un formsemestre_id
if formsemestre_id:
# Récupération du formsemestre
formsemestre = FormSemestre.get_formsemestre(formsemestre_id)
# Vériication si le module a été désactivé (avec la préférence)
pref: str | bool = has_assiduites_disable_pref(formsemestre)
# Le module est désactivé si on récupère un message d'erreur (str)
if pref:
raise ScoValueError(pref, dest_url=request.referrer)
return func(*args, **kwargs)
return decorated_function
# Gestion du cache # Gestion du cache
def get_assiduites_count(etudid: int, sem: dict) -> tuple[int, int, int]: def get_assiduites_count(etudid: int, sem: dict) -> tuple[int, int, int]:
"""Les comptes d'absences de cet étudiant dans ce semestre: """Les comptes d'absences de cet étudiant dans ce semestre:

View File

@ -61,6 +61,7 @@ from app.scodoc.sco_utils import ModuleType
from app.scodoc import html_sco_header from app.scodoc import html_sco_header
from app.scodoc import htmlutils from app.scodoc import htmlutils
from app.scodoc import sco_archives_formsemestre from app.scodoc import sco_archives_formsemestre
from app.scodoc import sco_assiduites as scass
from app.scodoc import sco_bulletins from app.scodoc import sco_bulletins
from app.scodoc import codes_cursus from app.scodoc import codes_cursus
from app.scodoc import sco_compute_moy from app.scodoc import sco_compute_moy
@ -783,6 +784,10 @@ def _make_listes_sem(formsemestre: FormSemestre) -> str:
) )
# #
H.append('<div class="sem-groups-abs">') H.append('<div class="sem-groups-abs">')
disable_abs: str | bool = scass.has_assiduites_disable_pref(formsemestre)
show_abs: str = "hidden" if disable_abs else ""
# Genere liste pour chaque partition (categorie de groupes) # Genere liste pour chaque partition (categorie de groupes)
for partition in formsemestre.get_partitions_list(): for partition in formsemestre.get_partitions_list():
groups = partition.groups.all() groups = partition.groups.all()
@ -797,10 +802,11 @@ def _make_listes_sem(formsemestre: FormSemestre) -> str:
('aucun étudiant inscrit' if partition_is_empty else 'Tous les étudiants')} ('aucun étudiant inscrit' if partition_is_empty else 'Tous les étudiants')}
</div> </div>
<div class="sem-groups-partition-titre">{ <div class="sem-groups-partition-titre">{
"Assiduité" if not partition_is_empty else "" "Assiduité" if not partition_is_empty and not show_abs else ""
}</div> }</div>
""" """
) )
if groups: if groups:
for group in groups: for group in groups:
n_members = effectifs[group.id] n_members = effectifs[group.id]
@ -821,8 +827,7 @@ def _make_listes_sem(formsemestre: FormSemestre) -> str:
- {n_members} étudiants</a> - {n_members} étudiants</a>
</div> </div>
</div> </div>
<div class="sem-groups-assi"> <div class="sem-groups-assi {show_abs}">
""" """
) )
if can_edit_abs: if can_edit_abs:
@ -911,7 +916,7 @@ def _make_listes_sem(formsemestre: FormSemestre) -> str:
) )
# --- Formulaire importation Assiduité excel (si autorisé) # --- Formulaire importation Assiduité excel (si autorisé)
if current_user.has_permission(Permission.AbsChange): if current_user.has_permission(Permission.AbsChange) and not disable_abs:
H.append( H.append(
f"""<p> f"""<p>
<a class="stdlink" href="{url_for('assiduites.feuille_abs_formsemestre', <a class="stdlink" href="{url_for('assiduites.feuille_abs_formsemestre',
@ -923,9 +928,11 @@ def _make_listes_sem(formsemestre: FormSemestre) -> str:
# --- Lien Traitement Justificatifs: # --- Lien Traitement Justificatifs:
if current_user.has_permission( if (
Permission.AbsJustifView current_user.has_permission(Permission.AbsJustifView)
) and current_user.has_permission(Permission.JustifValidate): and current_user.has_permission(Permission.JustifValidate)
and not disable_abs
):
H.append( H.append(
f"""<p> f"""<p>
<a class="stdlink" href="{url_for('assiduites.traitement_justificatifs', <a class="stdlink" href="{url_for('assiduites.traitement_justificatifs',
@ -936,6 +943,17 @@ def _make_listes_sem(formsemestre: FormSemestre) -> str:
) )
H.append("</div>") H.append("</div>")
if disable_abs:
H.append(
f"""
<div class="scobox" style="width:fit-content; font-style: italic;">
La gestion des absences est désactivée dans ScoDoc pour ce semestre:
{disable_abs}
</div>
"""
)
return "\n".join(H) return "\n".join(H)

View File

@ -42,6 +42,7 @@ from app import db
from app.models import FormSemestre, Identite from app.models import FormSemestre, Identite
import app.scodoc.sco_utils as scu import app.scodoc.sco_utils as scu
from app.scodoc import html_sco_header from app.scodoc import html_sco_header
from app.scodoc import sco_assiduites as scass
from app.scodoc import sco_excel from app.scodoc import sco_excel
from app.scodoc import sco_formsemestre from app.scodoc import sco_formsemestre
from app.scodoc import sco_groups from app.scodoc import sco_groups
@ -894,10 +895,9 @@ def tab_absences_html(groups_infos, etat=None):
group_ids: str = ",".join(map(str, groups_infos.group_ids)) group_ids: str = ",".join(map(str, groups_infos.group_ids))
formsemestre: FormSemestre = groups_infos.get_formsemestre() formsemestre: FormSemestre = groups_infos.get_formsemestre()
disable_abs: str | bool = scass.has_assiduites_disable_pref(formsemestre)
H.extend( liens_abs: list = [
[
"<h3>Assiduité</h3>",
'<ul class="ul_abs">', '<ul class="ul_abs">',
"<li>", "<li>",
form_choix_saisie_semaine(groups_infos), # Ajout Le Havre form_choix_saisie_semaine(groups_infos), # Ajout Le Havre
@ -913,6 +913,22 @@ def tab_absences_html(groups_infos, etat=None):
) )
}">État de l'assiduité du groupe</a></li>""", }">État de l'assiduité du groupe</a></li>""",
"</ul>", "</ul>",
]
if disable_abs:
liens_abs = [
f"""
<div class="scobox" style="width:fit-content; font-style:italic;">
La gestion des absences est désactivée dans ScoDoc pour ce semestre:
{disable_abs}
</div>
"""
]
H.extend(
[
"<h3>Assiduité</h3>",
*liens_abs,
"<h3>Feuilles</h3>", "<h3>Feuilles</h3>",
'<ul class="ul_feuilles">', '<ul class="ul_feuilles">',
"""<li><a class="stdlink" href="%s&fmt=xlsappel">Feuille d'émargement %s (Excel)</a></li>""" """<li><a class="stdlink" href="%s&fmt=xlsappel">Feuille d'émargement %s (Excel)</a></li>"""

View File

@ -40,6 +40,7 @@ from app.comp.res_common import ResultatsSemestre
from app.comp.res_compat import NotesTableCompat from app.comp.res_compat import NotesTableCompat
from app.models import Evaluation, FormSemestre, Module, ModuleImpl, UniteEns from app.models import Evaluation, FormSemestre, Module, ModuleImpl, UniteEns
import app.scodoc.sco_utils as scu import app.scodoc.sco_utils as scu
from app.scodoc import sco_assiduites as scass
from app.scodoc.codes_cursus import UE_SPORT from app.scodoc.codes_cursus import UE_SPORT
from app.scodoc.sco_exceptions import ScoInvalidIdType from app.scodoc.sco_exceptions import ScoInvalidIdType
from app.scodoc.sco_cursus_dut import formsemestre_has_decisions from app.scodoc.sco_cursus_dut import formsemestre_has_decisions
@ -68,6 +69,9 @@ def moduleimpl_evaluation_menu(evaluation: Evaluation, nbnotes: int = 0) -> str:
else: else:
sup_label = "Supprimer évaluation" sup_label = "Supprimer évaluation"
formsemestre: FormSemestre = FormSemestre.get_formsemestre(modimpl.formsemestre_id)
disable_abs: str | bool = scass.has_assiduites_disable_pref(formsemestre)
menu_eval = [ menu_eval = [
{ {
"title": "Saisir les notes", "title": "Saisir les notes",
@ -139,7 +143,8 @@ def moduleimpl_evaluation_menu(evaluation: Evaluation, nbnotes: int = 0) -> str:
), ),
}, },
"enabled": evaluation.date_debut is not None "enabled": evaluation.date_debut is not None
and evaluation.date_fin is not None, and evaluation.date_fin is not None
and not disable_abs,
}, },
{ {
"title": "Vérifier notes vs absents", "title": "Vérifier notes vs absents",
@ -147,7 +152,9 @@ def moduleimpl_evaluation_menu(evaluation: Evaluation, nbnotes: int = 0) -> str:
"args": { "args": {
"evaluation_id": evaluation_id, "evaluation_id": evaluation_id,
}, },
"enabled": nbnotes > 0 and evaluation.date_debut is not None, "enabled": nbnotes > 0
and evaluation.date_debut is not None
and not disable_abs,
}, },
] ]
@ -341,6 +348,10 @@ def moduleimpl_status(moduleimpl_id=None, partition_id=None):
else: else:
H.append('<tr><td colspan="4">') H.append('<tr><td colspan="4">')
H.append("</td></tr>") H.append("</td></tr>")
disable_abs: str | bool = scass.has_assiduites_disable_pref(formsemestre)
if not disable_abs:
H.append( H.append(
f"""<tr><td colspan="4"><span class="moduleimpl_abs_link"><a class="stdlink" f"""<tr><td colspan="4"><span class="moduleimpl_abs_link"><a class="stdlink"
href="{ href="{
@ -349,7 +360,11 @@ def moduleimpl_status(moduleimpl_id=None, partition_id=None):
) )
# Adapté à partir d'une suggestion de DS (Le Havre) # Adapté à partir d'une suggestion de DS (Le Havre)
# Liens saisies absences seulement si permission et date courante dans le semestre # Liens saisies absences seulement si permission et date courante dans le semestre
if current_user.has_permission(Permission.AbsChange) and formsemestre.est_courant(): if (
current_user.has_permission(Permission.AbsChange)
and formsemestre.est_courant()
and not disable_abs
):
group_id = sco_groups.get_default_group(formsemestre_id) group_id = sco_groups.get_default_group(formsemestre_id)
H.append( H.append(
f""" f"""

View File

@ -685,6 +685,19 @@ class BasePreferences:
"only_global": True, "only_global": True,
}, },
), ),
(
"assiduites_disable",
{
"initvalue": "",
"title": "Désactiver le module d'assiduité",
"size": 40,
"category": "assi",
"explanation": """Désactive complètement le suivi de l'assiduité sur ScoDoc.
Indiquer un message à afficher pour orienter l'utilisateur.
Laisser ce champ est vide pour utiliser l'assiduité dans ScoDoc.
""",
},
),
( (
"abs_notify_max_freq", "abs_notify_max_freq",
{ {

View File

@ -4932,3 +4932,7 @@ div.cas_etat_certif_ssl {
font-style: italic; font-style: italic;
color: rgb(231, 0, 0); color: rgb(231, 0, 0);
} }
.hidden {
visibility: hidden;
}

View File

@ -96,6 +96,7 @@ from app.scodoc.sco_archives_justificatifs import JustificatifArchiver
CSSSTYLES = html_sco_header.BOOTSTRAP_MULTISELECT_CSS CSSSTYLES = html_sco_header.BOOTSTRAP_MULTISELECT_CSS
# -------------------------------------------------------------------- # --------------------------------------------------------------------
# #
# Assiduité (/ScoDoc/<dept>/Scolarite/Assiduites/...) # Assiduité (/ScoDoc/<dept>/Scolarite/Assiduites/...)
@ -106,6 +107,7 @@ CSSSTYLES = html_sco_header.BOOTSTRAP_MULTISELECT_CSS
@bp.route("/") @bp.route("/")
@bp.route("/bilan_dept") @bp.route("/bilan_dept")
@scodoc @scodoc
@scass.check_disabled
@permission_required(Permission.AbsChange) @permission_required(Permission.AbsChange)
def bilan_dept(): def bilan_dept():
"""Gestionnaire assiduités, page principale""" """Gestionnaire assiduités, page principale"""
@ -212,6 +214,7 @@ def bilan_dept():
@bp.route("/ajout_assiduite_etud", methods=["GET", "POST"]) @bp.route("/ajout_assiduite_etud", methods=["GET", "POST"])
@scodoc @scodoc
@scass.check_disabled
@permission_required(Permission.AbsChange) @permission_required(Permission.AbsChange)
def ajout_assiduite_etud() -> str | Response: def ajout_assiduite_etud() -> str | Response:
""" """
@ -1016,6 +1019,7 @@ def calendrier_assi_etud():
@bp.route("/signal_assiduites_group") @bp.route("/signal_assiduites_group")
@scodoc @scodoc
@scass.check_disabled
@permission_required(Permission.AbsChange) @permission_required(Permission.AbsChange)
def signal_assiduites_group(): def signal_assiduites_group():
""" """
@ -1324,6 +1328,11 @@ def visu_assi_group():
# Récupération des groupes, du semestre et des étudiants # Récupération des groupes, du semestre et des étudiants
groups_infos = sco_groups_view.DisplayedGroupsInfos(group_ids) groups_infos = sco_groups_view.DisplayedGroupsInfos(group_ids)
formsemestre = db.session.get(FormSemestre, groups_infos.formsemestre_id) formsemestre = db.session.get(FormSemestre, groups_infos.formsemestre_id)
# Vérification de la désactivation de l'assiduité
if err_msg := scass.has_assiduites_disable_pref(formsemestre):
raise ScoValueError(err_msg, request.referrer)
etuds = etuds_sorted_from_ids([m["etudid"] for m in groups_infos.members]) etuds = etuds_sorted_from_ids([m["etudid"] for m in groups_infos.members])
# Génération du tableau des assiduités # Génération du tableau des assiduités
@ -1795,6 +1804,7 @@ def signale_evaluation_abs(etudid: int = None, evaluation_id: int = None):
@scodoc @scodoc
@permission_required(Permission.JustifValidate) @permission_required(Permission.JustifValidate)
@permission_required(Permission.AbsJustifView) @permission_required(Permission.AbsJustifView)
@scass.check_disabled
def traitement_justificatifs(): def traitement_justificatifs():
"""Page de traitement des justificatifs """Page de traitement des justificatifs
On traite les justificatifs par formsemestre On traite les justificatifs par formsemestre
@ -1864,6 +1874,7 @@ def traitement_justificatifs():
@bp.route("signal_assiduites_hebdo") @bp.route("signal_assiduites_hebdo")
@scodoc @scodoc
@permission_required(Permission.ScoView) @permission_required(Permission.ScoView)
@scass.check_disabled
def signal_assiduites_hebdo(): def signal_assiduites_hebdo():
""" """
signal_assiduites_hebdo signal_assiduites_hebdo
@ -2049,6 +2060,10 @@ def edit_assiduite_etud(assiduite_id: int):
etud: Identite = assi.etudiant etud: Identite = assi.etudiant
formsemestre: FormSemestre = assi.get_formsemestre() formsemestre: FormSemestre = assi.get_formsemestre()
# Vérification de la désactivation de l'assiduité
if err_msg := scass.has_assiduites_disable_pref(formsemestre):
raise ScoValueError(err_msg, request.referrer)
readonly: bool = not current_user.has_permission(Permission.AbsChange) readonly: bool = not current_user.has_permission(Permission.AbsChange)
form: EditAssiForm = EditAssiForm(request.form) form: EditAssiForm = EditAssiForm(request.form)
@ -2228,6 +2243,7 @@ def generate_bul_list(etud: Identite, semestre: FormSemestre) -> str:
@bp.route("feuille_abs_hebdo", methods=["GET", "POST"]) @bp.route("feuille_abs_hebdo", methods=["GET", "POST"])
@scodoc @scodoc
@permission_required(Permission.AbsChange) @permission_required(Permission.AbsChange)
@scass.check_disabled
def feuille_abs_hebdo(): def feuille_abs_hebdo():
""" """
GET : Renvoie un tableau excel pour permettre la saisie des absences GET : Renvoie un tableau excel pour permettre la saisie des absences
@ -2374,6 +2390,8 @@ def feuille_abs_hebdo():
@bp.route("feuille_abs_formsemestre", methods=["GET", "POST"]) @bp.route("feuille_abs_formsemestre", methods=["GET", "POST"])
@scodoc @scodoc
@permission_required(Permission.AbsChange)
@scass.check_disabled
def feuille_abs_formsemestre(): def feuille_abs_formsemestre():
""" """
Permet l'importation d'une liste d'assiduités depuis un fichier excel Permet l'importation d'une liste d'assiduités depuis un fichier excel