From d4f206cc3e43bc4ed3dc05c2332576895f36f2ad Mon Sep 17 00:00:00 2001 From: Iziram Date: Tue, 9 Jul 2024 14:31:15 +0200 Subject: [PATCH 1/3] =?UTF-8?q?Assiduit=C3=A9=20:=20D=C3=A9sactivation=20d?= =?UTF-8?q?u=20module=20(pr=C3=A9f=C3=A9rence=20semestre)=20closes=20#67?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/models/assiduites.py | 33 ++++++++++++++++++++++++++++++ app/scodoc/sco_assiduites.py | 38 +++++++++++++++++++++++++++++++++-- app/scodoc/sco_preferences.py | 14 ++++++++++--- app/views/assiduites.py | 18 +++++++++++++++++ 4 files changed, 98 insertions(+), 5 deletions(-) diff --git a/app/models/assiduites.py b/app/models/assiduites.py index 29b8b889..297a4014 100644 --- a/app/models/assiduites.py +++ b/app/models/assiduites.py @@ -21,6 +21,7 @@ from app.scodoc import sco_abs_notification from app.scodoc.sco_archives_justificatifs import JustificatifArchiver from app.scodoc.sco_exceptions import ScoValueError from app.scodoc.sco_permissions import Permission +from app.scodoc import sco_preferences from app.scodoc import sco_utils as scu from app.scodoc.sco_utils import ( EtatAssiduite, @@ -188,6 +189,12 @@ class Assiduite(ScoDocModel): ): 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 assiduites: Query = etud.assiduites 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() ) + + +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 diff --git a/app/scodoc/sco_assiduites.py b/app/scodoc/sco_assiduites.py index 6626c903..f085a83f 100644 --- a/app/scodoc/sco_assiduites.py +++ b/app/scodoc/sco_assiduites.py @@ -3,9 +3,10 @@ Ecrit par Matthias Hartmann. """ from datetime import date, datetime, time, timedelta +from functools import wraps from pytz import UTC -from flask import g +from flask import g, request from flask_sqlalchemy.query import Query from app import log, db, set_sco_dept @@ -18,12 +19,13 @@ from app.models import ( ModuleImplInscription, 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_preferences from app.scodoc import sco_cache from app.scodoc import sco_etud import app.scodoc.sco_utils as scu +from app.scodoc.sco_exceptions import ScoValueError class CountCalculator: @@ -819,6 +821,38 @@ def get_etud_evaluations_assiduites(etud: Identite) -> list[dict]: 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 def get_assiduites_count(etudid: int, sem: dict) -> tuple[int, int, int]: """Les comptes d'absences de cet étudiant dans ce semestre: diff --git a/app/scodoc/sco_preferences.py b/app/scodoc/sco_preferences.py index 019cec65..fb1021cb 100644 --- a/app/scodoc/sco_preferences.py +++ b/app/scodoc/sco_preferences.py @@ -625,6 +625,16 @@ class BasePreferences: "explanation": "Désactive la saisie et l'affichage des présences", }, ), + ( + "assiduites_disable", + { + "initvalue": "", + "title": "Désactiver le module d'assiduité", + "size": 40, + "category": "assi", + "explanation": "Laisser vide pour ne pas désactiver. Sinon mettre un message à afficher", + }, + ), ( "nb_heures_par_jour", { @@ -2293,9 +2303,7 @@ class BasePreferences: if "explanation" in descr: del descr["explanation"] if formsemestre_id: - descr[ - "explanation" - ] = f"""ou utiliser paramètre global""" if formsemestre_id and self.is_global(formsemestre_id, pref_name): diff --git a/app/views/assiduites.py b/app/views/assiduites.py index 8db1da15..10e092cb 100644 --- a/app/views/assiduites.py +++ b/app/views/assiduites.py @@ -96,6 +96,7 @@ from app.scodoc.sco_archives_justificatifs import JustificatifArchiver CSSSTYLES = html_sco_header.BOOTSTRAP_MULTISELECT_CSS + # -------------------------------------------------------------------- # # Assiduité (/ScoDoc//Scolarite/Assiduites/...) @@ -106,6 +107,7 @@ CSSSTYLES = html_sco_header.BOOTSTRAP_MULTISELECT_CSS @bp.route("/") @bp.route("/bilan_dept") @scodoc +@scass.check_disabled @permission_required(Permission.AbsChange) def bilan_dept(): """Gestionnaire assiduités, page principale""" @@ -212,6 +214,7 @@ def bilan_dept(): @bp.route("/ajout_assiduite_etud", methods=["GET", "POST"]) @scodoc +@scass.check_disabled @permission_required(Permission.AbsChange) def ajout_assiduite_etud() -> str | Response: """ @@ -1016,6 +1019,7 @@ def calendrier_assi_etud(): @bp.route("/signal_assiduites_group") @scodoc +@scass.check_disabled @permission_required(Permission.AbsChange) def signal_assiduites_group(): """ @@ -1324,6 +1328,11 @@ def visu_assi_group(): # Récupération des groupes, du semestre et des étudiants groups_infos = sco_groups_view.DisplayedGroupsInfos(group_ids) 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]) # Génération du tableau des assiduités @@ -1795,6 +1804,7 @@ def signale_evaluation_abs(etudid: int = None, evaluation_id: int = None): @scodoc @permission_required(Permission.JustifValidate) @permission_required(Permission.AbsJustifView) +@scass.check_disabled def traitement_justificatifs(): """Page de traitement des justificatifs On traite les justificatifs par formsemestre @@ -1864,6 +1874,7 @@ def traitement_justificatifs(): @bp.route("signal_assiduites_hebdo") @scodoc @permission_required(Permission.ScoView) +@scass.check_disabled def signal_assiduites_hebdo(): """ signal_assiduites_hebdo @@ -2049,6 +2060,10 @@ def edit_assiduite_etud(assiduite_id: int): etud: Identite = assi.etudiant 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) 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"]) @scodoc @permission_required(Permission.AbsChange) +@scass.check_disabled def feuille_abs_hebdo(): """ 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"]) @scodoc +@permission_required(Permission.AbsChange) +@scass.check_disabled def feuille_abs_formsemestre(): """ Permet l'importation d'une liste d'assiduités depuis un fichier excel From 72c66961516692fd1b16895bbf5cd2daa83f0abc Mon Sep 17 00:00:00 2001 From: Iziram Date: Tue, 9 Jul 2024 15:23:36 +0200 Subject: [PATCH 2/3] =?UTF-8?q?Assiduit=C3=A9=20:=20non=20affichage=20si?= =?UTF-8?q?=20pr=C3=A9f=C3=A9rence?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/scodoc/sco_formsemestre_status.py | 30 +++++++++++++++----- app/scodoc/sco_groups_view.py | 40 +++++++++++++++++++-------- app/scodoc/sco_moduleimpl_status.py | 27 ++++++++++++++---- app/static/css/scodoc.css | 4 +++ 4 files changed, 76 insertions(+), 25 deletions(-) diff --git a/app/scodoc/sco_formsemestre_status.py b/app/scodoc/sco_formsemestre_status.py index 4c3748f4..3fb89f5f 100755 --- a/app/scodoc/sco_formsemestre_status.py +++ b/app/scodoc/sco_formsemestre_status.py @@ -61,6 +61,7 @@ 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_assiduites as scass from app.scodoc import sco_bulletins from app.scodoc import codes_cursus from app.scodoc import sco_compute_moy @@ -783,6 +784,10 @@ def _make_listes_sem(formsemestre: FormSemestre) -> str: ) # H.append('
') + + 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) for partition in formsemestre.get_partitions_list(): 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')}
{ - "Assiduité" if not partition_is_empty else "" + "Assiduité" if not partition_is_empty and not show_abs else "" }
""" ) + if groups: for group in groups: n_members = effectifs[group.id] @@ -821,8 +827,7 @@ def _make_listes_sem(formsemestre: FormSemestre) -> str: - {n_members} étudiants -
- +
""" ) if can_edit_abs: @@ -911,7 +916,7 @@ def _make_listes_sem(formsemestre: FormSemestre) -> str: ) # --- 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( f"""

") + + if disable_abs: + H.append(f""" +

+ """) + return "\n".join(H) diff --git a/app/scodoc/sco_groups_view.py b/app/scodoc/sco_groups_view.py index e7858f8f..87ba50e6 100644 --- a/app/scodoc/sco_groups_view.py +++ b/app/scodoc/sco_groups_view.py @@ -42,6 +42,7 @@ from app import db from app.models import FormSemestre, Identite import app.scodoc.sco_utils as scu 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_formsemestre from app.scodoc import sco_groups @@ -894,25 +895,40 @@ def tab_absences_html(groups_infos, etat=None): group_ids: str = ",".join(map(str, groups_infos.group_ids)) formsemestre: FormSemestre = groups_infos.get_formsemestre() + disable_abs: str | bool = scass.has_assiduites_disable_pref(formsemestre) - H.extend( - [ - "

Assiduité

", - '
", + "", + ] + + if disable_abs: + liens_abs = [ + f""" +
+

La gestion des absences est désactivée dans ScoDoc pour ce semestre:

+

{disable_abs}

+
+ """ + ] + + H.extend( + [ + "

Assiduité

", + *liens_abs, "

Feuilles

", '
    ', """
  • Feuille d'émargement %s (Excel)
  • """ diff --git a/app/scodoc/sco_moduleimpl_status.py b/app/scodoc/sco_moduleimpl_status.py index 87e8fc43..470be3e8 100644 --- a/app/scodoc/sco_moduleimpl_status.py +++ b/app/scodoc/sco_moduleimpl_status.py @@ -40,6 +40,7 @@ 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 import sco_assiduites as scass 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 @@ -68,6 +69,9 @@ def moduleimpl_evaluation_menu(evaluation: Evaluation, nbnotes: int = 0) -> str: else: sup_label = "Supprimer évaluation" + formsemestre: FormSemestre = FormSemestre.get_formsemestre(modimpl.formsemestre_id) + disable_abs: str | bool = scass.has_assiduites_disable_pref(formsemestre) + menu_eval = [ { "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 - and evaluation.date_fin is not None, + and evaluation.date_fin is not None + and not disable_abs, }, { "title": "Vérifier notes vs absents", @@ -147,7 +152,9 @@ def moduleimpl_evaluation_menu(evaluation: Evaluation, nbnotes: int = 0) -> str: "args": { "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,15 +348,23 @@ def moduleimpl_status(moduleimpl_id=None, partition_id=None): else: H.append('') 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.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) H.append( f""" diff --git a/app/static/css/scodoc.css b/app/static/css/scodoc.css index b3cc3d96..4974a4e1 100644 --- a/app/static/css/scodoc.css +++ b/app/static/css/scodoc.css @@ -4931,4 +4931,8 @@ div.cas_etat_certif_ssl { margin-bottom: 8px; font-style: italic; color: rgb(231, 0, 0); +} + +.hidden { + visibility: hidden; } \ No newline at end of file From cc2fe778503c7b9fdeca0dd0f77a14a5d84e497b Mon Sep 17 00:00:00 2001 From: Emmanuel Viennet Date: Wed, 10 Jul 2024 21:31:36 +0200 Subject: [PATCH 3/3] cosmetic --- app/scodoc/sco_formsemestre_status.py | 12 +++++++----- app/scodoc/sco_groups_view.py | 6 +++--- app/scodoc/sco_preferences.py | 27 ++++++++++++++++----------- 3 files changed, 26 insertions(+), 19 deletions(-) diff --git a/app/scodoc/sco_formsemestre_status.py b/app/scodoc/sco_formsemestre_status.py index 3fb89f5f..19ebe1fc 100755 --- a/app/scodoc/sco_formsemestre_status.py +++ b/app/scodoc/sco_formsemestre_status.py @@ -945,12 +945,14 @@ def _make_listes_sem(formsemestre: FormSemestre) -> str: H.append("
") if disable_abs: - H.append(f""" -
-

La gestion des absences est désactivée dans ScoDoc pour ce semestre:

-

{disable_abs}

+ H.append( + f""" +
+ La gestion des absences est désactivée dans ScoDoc pour ce semestre: + {disable_abs}
- """) + """ + ) return "\n".join(H) diff --git a/app/scodoc/sco_groups_view.py b/app/scodoc/sco_groups_view.py index 87ba50e6..86d7db49 100644 --- a/app/scodoc/sco_groups_view.py +++ b/app/scodoc/sco_groups_view.py @@ -918,9 +918,9 @@ def tab_absences_html(groups_infos, etat=None): if disable_abs: liens_abs = [ f""" -
-

La gestion des absences est désactivée dans ScoDoc pour ce semestre:

-

{disable_abs}

+
+ La gestion des absences est désactivée dans ScoDoc pour ce semestre: + {disable_abs}
""" ] diff --git a/app/scodoc/sco_preferences.py b/app/scodoc/sco_preferences.py index fb1021cb..457aaa7d 100644 --- a/app/scodoc/sco_preferences.py +++ b/app/scodoc/sco_preferences.py @@ -625,16 +625,6 @@ class BasePreferences: "explanation": "Désactive la saisie et l'affichage des présences", }, ), - ( - "assiduites_disable", - { - "initvalue": "", - "title": "Désactiver le module d'assiduité", - "size": 40, - "category": "assi", - "explanation": "Laisser vide pour ne pas désactiver. Sinon mettre un message à afficher", - }, - ), ( "nb_heures_par_jour", { @@ -695,6 +685,19 @@ class BasePreferences: "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", { @@ -2303,7 +2306,9 @@ class BasePreferences: if "explanation" in descr: del descr["explanation"] if formsemestre_id: - descr["explanation"] = f"""ou utiliser paramètre global""" if formsemestre_id and self.is_global(formsemestre_id, pref_name):