From f7186c63163533b096b5612e303f7c639259e5db Mon Sep 17 00:00:00 2001 From: Emmanuel Viennet Date: Sun, 27 Aug 2023 21:49:50 +0200 Subject: [PATCH] =?UTF-8?q?Enl=C3=A8ve=20l'ancien=20module=20de=20gestion?= =?UTF-8?q?=20des=20absences?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/api/__init__.py | 1 - app/api/absences.py | 263 ------ app/scodoc/sco_abs_views.py | 1094 ------------------------ app/scodoc/sco_assiduites.py | 42 +- app/scodoc/sco_bulletins.py | 9 +- app/scodoc/sco_bulletins_legacy.py | 15 +- app/scodoc/sco_bulletins_standard.py | 3 + app/scodoc/sco_evaluation_check_abs.py | 54 +- app/scodoc/sco_evaluations.py | 4 +- app/scodoc/sco_formsemestre_status.py | 15 +- app/scodoc/sco_groups_view.py | 21 +- app/scodoc/sco_moduleimpl.py | 2 +- app/scodoc/sco_moduleimpl_status.py | 21 +- app/scodoc/sco_saisie_notes.py | 13 +- app/scodoc/sco_trombino_tours.py | 6 +- app/static/js/abs_ajax.js | 47 - app/views/absences.py | 1071 +---------------------- app/views/assiduites.py | 1 - app/views/notes.py | 217 ++--- tests/unit/test_abs_counts.py | 117 --- tests/unit/test_abs_demijournee.py | 344 -------- tests/unit/test_assiduites.py | 12 +- tests/unit/test_sco_basic.py | 27 +- 23 files changed, 202 insertions(+), 3197 deletions(-) delete mode 100644 app/api/absences.py delete mode 100644 app/scodoc/sco_abs_views.py delete mode 100644 app/static/js/abs_ajax.js delete mode 100644 tests/unit/test_abs_counts.py delete mode 100644 tests/unit/test_abs_demijournee.py diff --git a/app/api/__init__.py b/app/api/__init__.py index e2d7a95f..b94cf855 100644 --- a/app/api/__init__.py +++ b/app/api/__init__.py @@ -66,7 +66,6 @@ def get_model_api_object(model_cls: db.Model, model_id: int, join_cls: db.Model from app.api import tokens from app.api import ( - absences, assiduites, billets_absences, departements, diff --git a/app/api/absences.py b/app/api/absences.py deleted file mode 100644 index acd690ff..00000000 --- a/app/api/absences.py +++ /dev/null @@ -1,263 +0,0 @@ -############################################################################## -# ScoDoc -# Copyright (c) 1999 - 2023 Emmanuel Viennet. All rights reserved. -# See LICENSE -############################################################################## -"""ScoDoc 9 API : Absences -""" - -from flask_json import as_json - -from app import db -from app.api import api_bp as bp, API_CLIENT_ERROR -from app.scodoc.sco_utils import json_error -from app.decorators import scodoc, permission_required -from app.models import Identite - -from app.scodoc import notesdb as ndb -from app.scodoc import sco_abs - -from app.scodoc.sco_groups import get_group_members -from app.scodoc.sco_permissions import Permission - - -# TODO XXX revoir routes web API et calcul des droits -@bp.route("/absences/etudid/", methods=["GET"]) -@scodoc -@permission_required(Permission.ScoView) -@as_json -def absences(etudid: int = None): - """ - Liste des absences de cet étudiant - - Exemple de résultat: - [ - { - "jour": "2022-04-15", - "matin": true, - "estabs": true, - "estjust": true, - "description": "", - "begin": "2022-04-15 08:00:00", - "end": "2022-04-15 11:59:59" - }, - { - "jour": "2022-04-15", - "matin": false, - "estabs": true, - "estjust": false, - "description": "", - "begin": "2022-04-15 12:00:00", - "end": "2022-04-15 17:59:59" - } - ] - """ - etud = db.session.get(Identite, etudid) - if etud is None: - return json_error(404, message="etudiant inexistant") - # Absences de l'étudiant - ndb.open_db_connection() - abs_list = sco_abs.list_abs_date(etud.id) - for absence in abs_list: - absence["jour"] = absence["jour"].isoformat() - return abs_list - - -@bp.route("/absences/etudid//just", methods=["GET"]) -@scodoc -@permission_required(Permission.ScoView) -@as_json -def absences_just(etudid: int = None): - """ - Retourne la liste des absences justifiées d'un étudiant donné - - etudid : l'etudid d'un étudiant - nip: le code nip d'un étudiant - ine : le code ine d'un étudiant - - Exemple de résultat : - [ - { - "jour": "2022-04-15", - "matin": true, - "estabs": true, - "estjust": true, - "description": "", - "begin": "2022-04-15 08:00:00", - "end": "2022-04-15 11:59:59" - }, - { - "jour": "Fri, 15 Apr 2022 00:00:00 GMT", - "matin": false, - "estabs": true, - "estjust": true, - "description": "", - "begin": "2022-04-15 12:00:00", - "end": "2022-04-15 17:59:59" - } - ] - """ - etud = db.session.get(Identite, etudid) - if etud is None: - return json_error(404, message="etudiant inexistant") - - # Absences justifiées de l'étudiant - abs_just = [ - absence for absence in sco_abs.list_abs_date(etud.id) if absence["estjust"] - ] - for absence in abs_just: - absence["jour"] = absence["jour"].isoformat() - return abs_just - - -@bp.route( - "/absences/abs_group_etat/", - methods=["GET"], -) -@bp.route( - "/absences/abs_group_etat/group_id//date_debut//date_fin/", - methods=["GET"], -) -@scodoc -@permission_required(Permission.ScoView) -@as_json -def abs_groupe_etat(group_id: int, date_debut=None, date_fin=None): - """ - Liste des absences d'un groupe (possibilité de choisir entre deux dates) - - group_id = l'id du groupe - date_debut = None par défaut, sinon la date ISO du début de notre filtre - date_fin = None par défaut, sinon la date ISO de la fin de notre filtre - - Exemple de résultat : - [ - { - "etudid": 1, - "list_abs": [] - }, - { - "etudid": 2, - "list_abs": [ - { - "jour": "Fri, 15 Apr 2022 00:00:00 GMT", - "matin": true, - "estabs": true, - "estjust": true, - "description": "", - "begin": "2022-04-15 08:00:00", - "end": "2022-04-15 11:59:59" - }, - { - "jour": "Fri, 15 Apr 2022 00:00:00 GMT", - "matin": false, - "estabs": true, - "estjust": false, - "description": "", - "begin": "2022-04-15 12:00:00", - "end": "2022-04-15 17:59:59" - }, - ] - }, - ... - ] - """ - members = get_group_members(group_id) - - data = [] - # Filtre entre les deux dates renseignées - for member in members: - absence = { - "etudid": member["etudid"], - "list_abs": sco_abs.list_abs_date(member["etudid"], date_debut, date_fin), - } - data.append(absence) - - return data - - -# XXX TODO EV: A REVOIR (data json dans le POST + modifier les routes) -# @bp.route( -# "/absences/etudid//list_abs//reset_etud_abs", -# methods=["POST"], -# defaults={"just_or_not": 0}, -# ) -# @bp.route( -# "/absences/etudid//list_abs//reset_etud_abs/only_not_just", -# methods=["POST"], -# defaults={"just_or_not": 1}, -# ) -# @bp.route( -# "/absences/etudid//list_abs//reset_etud_abs/only_just", -# methods=["POST"], -# defaults={"just_or_not": 2}, -# ) -# @token_auth.login_required -# @token_permission_required(Permission.APIAbsChange) -# def reset_etud_abs(etudid: int, list_abs: str, just_or_not: int = 0): -# """ -# Set la liste des absences d'un étudiant sur tout un semestre. -# (les absences existant pour cet étudiant sur cette période sont effacées) - -# etudid : l'id d'un étudiant -# list_abs : json d'absences -# just_or_not : 0 (pour les absences justifiées et non justifiées), -# 1 (pour les absences justifiées), -# 2 (pour les absences non justifiées) -# """ -# # Toutes les absences -# if just_or_not == 0: -# # suppression des absences et justificatif déjà existant pour éviter les doublons -# for abs in list_abs: -# # Récupération de la date au format iso -# jour = abs["jour"].isoformat() -# if abs["matin"] is True: -# annule_absence(etudid, jour, True) -# annule_justif(etudid, jour, True) -# else: -# annule_absence(etudid, jour, False) -# annule_justif(etudid, jour, False) - -# # Ajout de la liste d'absences en base -# add_abslist(list_abs) - -# # Uniquement les absences justifiées -# elif just_or_not == 1: -# list_abs_not_just = [] -# # Trie des absences justifiées -# for abs in list_abs: -# if abs["estjust"] is False: -# list_abs_not_just.append(abs) -# # suppression des absences et justificatif déjà existant pour éviter les doublons -# for abs in list_abs: -# # Récupération de la date au format iso -# jour = abs["jour"].isoformat() -# if abs["matin"] is True: -# annule_absence(etudid, jour, True) -# annule_justif(etudid, jour, True) -# else: -# annule_absence(etudid, jour, False) -# annule_justif(etudid, jour, False) - -# # Ajout de la liste d'absences en base -# add_abslist(list_abs_not_just) - -# # Uniquement les absences non justifiées -# elif just_or_not == 2: -# list_abs_just = [] -# # Trie des absences non justifiées -# for abs in list_abs: -# if abs["estjust"] is True: -# list_abs_just.append(abs) -# # suppression des absences et justificatif déjà existant pour éviter les doublons -# for abs in list_abs: -# # Récupération de la date au format iso -# jour = abs["jour"].isoformat() -# if abs["matin"] is True: -# annule_absence(etudid, jour, True) -# annule_justif(etudid, jour, True) -# else: -# annule_absence(etudid, jour, False) -# annule_justif(etudid, jour, False) - -# # Ajout de la liste d'absences en base -# add_abslist(list_abs_just) diff --git a/app/scodoc/sco_abs_views.py b/app/scodoc/sco_abs_views.py deleted file mode 100644 index c6f99a2b..00000000 --- a/app/scodoc/sco_abs_views.py +++ /dev/null @@ -1,1094 +0,0 @@ -# -*- 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 -# -############################################################################## - -"""Pages HTML gestion absences - (la plupart portées du DTML) -""" -import datetime - -from flask import url_for, g, request, abort - -from app import log -from app.comp import res_sem -from app.comp.res_compat import NotesTableCompat -from app.models import Identite, FormSemestre -import app.scodoc.sco_utils as scu -from app.scodoc import notesdb as ndb -from app.scodoc.scolog import logdb -from app.scodoc.gen_tables import GenTable -from app.scodoc import html_sco_header -from app.scodoc import sco_abs -from app.scodoc import sco_etud -from app.scodoc import sco_find_etud -from app.scodoc import sco_formsemestre -from app.scodoc import sco_groups -from app.scodoc import sco_moduleimpl -from app.scodoc import sco_photos -from app.scodoc import sco_preferences -from app.scodoc.sco_exceptions import ScoValueError - - -def doSignaleAbsence( - datedebut, - datefin, - moduleimpl_id=None, - demijournee=2, - estjust=False, - description=None, - etudid=False, -): # etudid implied - """Signalement d'une absence. - - Args: - datedebut: dd/mm/yyyy - datefin: dd/mm/yyyy (non incluse) - moduleimpl_id: module auquel imputer les absences - demijournee: 2 si journée complète, 1 matin, 0 après-midi - estjust: absence justifiée - description: str - etudid: etudiant concerné. Si non spécifié, cherche dans - les paramètres de la requête courante. - """ - etud = Identite.from_request(etudid) - - if not moduleimpl_id: - moduleimpl_id = None - description_abs = description - dates = sco_abs.DateRangeISO(datedebut, datefin) - nbadded = 0 - demijournee = int(demijournee) - for jour in dates: - if demijournee == 2: - sco_abs.add_absence( - etud.id, - jour, - False, - estjust, - description_abs, - moduleimpl_id, - ) - sco_abs.add_absence( - etud.id, - jour, - True, - estjust, - description_abs, - moduleimpl_id, - ) - nbadded += 2 - else: - sco_abs.add_absence( - etud.id, - jour, - demijournee, - estjust, - description_abs, - moduleimpl_id, - ) - nbadded += 1 - # - if estjust: - just_str = "" - else: - just_str = "NON " - indication_module = "" - if moduleimpl_id and moduleimpl_id != "NULL": - mod = sco_moduleimpl.moduleimpl_list(moduleimpl_id=moduleimpl_id)[0] - formsemestre_id = mod["formsemestre_id"] - formsemestre = FormSemestre.get_formsemestre(formsemestre_id) - nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre) - ues = nt.get_ues_stat_dict() - for ue in ues: - modimpls = nt.get_modimpls_dict(ue_id=ue["ue_id"]) - for modimpl in modimpls: - if modimpl["moduleimpl_id"] == moduleimpl_id: - indication_module = "dans le module %s" % ( - modimpl["module"]["code"] or "(pas de code)" - ) - H = [ - html_sco_header.sco_header( - page_title=f"Signalement d'une absence pour {etud.nomprenom}", - ), - """

Signalement d'absences

""", - ] - if dates: - H.append( - f"""

Ajout de {nbadded} absences {just_str}justifiées - du {datedebut} au {datefin} {indication_module} -

- """ - ) - else: - H.append( - f"""

Aucune date ouvrable - entre le {datedebut} et le {datefin} ! -

- """ - ) - - H.append( - f""" -
- {sco_find_etud.form_search_etud()} - {html_sco_header.sco_footer()} - """ - ) - return "\n".join(H) - - -def SignaleAbsenceEtud(): # etudid implied - """Formulaire individuel simple de signalement d'une absence""" - # brute-force portage from very old dtml code ... - etud = sco_etud.get_etud_info(filled=True)[0] - etudid = etud["etudid"] - disabled = False - if not etud["cursem"]: - require_module = sco_preferences.get_preference( - "abs_require_module" - ) # on utilise la pref globale car pas de sem courant - if require_module: - menu_module = """
Pas - inscrit dans un semestre courant, - et l'indication du module est requise. - Donc pas de saisie d'absence possible ! -
- """ - disabled = True - else: - menu_module = "" - else: - formsemestre_id = etud["cursem"]["formsemestre_id"] - formsemestre = FormSemestre.get_formsemestre(formsemestre_id) - nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre) - ues = nt.get_ues_stat_dict() - require_module = sco_preferences.get_preference( - "abs_require_module", formsemestre_id - ) - if require_module: - menu_module = """ - -

Module: - """ - ) - menu_module += """""" - - for ue in ues: - modimpls = nt.get_modimpls_dict(ue_id=ue["ue_id"]) - for modimpl in modimpls: - menu_module += ( - """\n""" - % { - "modimpl_id": modimpl["moduleimpl_id"], - "modname": modimpl["module"]["code"] or "", - } - ) - menu_module += """

""" - - H = [ - html_sco_header.sco_header( - page_title="Signalement d'une absence pour %(nomprenom)s" % etud, - ), - """
-

Signalement d'une absence pour %(nomprenom)s

-
- """ - % etud, - """""" - % url_for("scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=etud["etudid"]), - sco_photos.etud_photo_html( - etudid=etudid, - title="fiche de " + etud["nomprenom"], - ), - """
""", - """ -
- -

- - - - - - -
Date début : - - j/m/a -   Date fin (optionnelle): j/m/a
-
-Journée(s) - Matin(s) - Après-midi - -%(menu_module)s - -

-Absence justifiée. -
-Raison: (optionnel) -

- -

- - -

Seuls les modules du semestre en cours apparaissent.

-

Évitez de saisir une absence pour un module qui n'est pas en place à cette date.

-

Toutes les dates sont au format jour/mois/annee.

-
- -
- """ - % { - "etudid": etud["etudid"], - "menu_module": menu_module, - "disabled": "disabled" if disabled else "", - }, - html_sco_header.sco_footer(), - ] - return "\n".join(H) - - -def doJustifAbsence( - datedebut, - datefin, - demijournee, - description=None, - etudid=False, -): # etudid implied - """Justification d'une absence - - Args: - datedebut: dd/mm/yyyy - datefin: dd/mm/yyyy (non incluse) - demijournee: 2 si journée complète, 1 matin, 0 après-midi - estjust: absence justifiée - description: str - etudid: etudiant concerné. Si non spécifié, cherche dans les - paramètres de la requête. - """ - etud = sco_etud.get_etud_info(filled=True, etudid=etudid)[0] - etudid = etud["etudid"] - description_abs = description - dates = sco_abs.DateRangeISO(datedebut, datefin) - nbadded = 0 - demijournee = int(demijournee) - for jour in dates: - if demijournee == 2: - sco_abs.add_justif( - etudid=etudid, - jour=jour, - matin=False, - description=description_abs, - ) - sco_abs.add_justif( - etudid=etudid, - jour=jour, - matin=True, - description=description_abs, - ) - nbadded += 2 - else: - sco_abs.add_justif( - etudid=etudid, - jour=jour, - matin=demijournee, - description=description_abs, - ) - nbadded += 1 - # - H = [ - html_sco_header.sco_header( - page_title="Justification d'une absence pour %(nomprenom)s" % etud, - ), - """

Justification d'absences

""", - ] - if dates: - H.append( - """

Ajout de %d justifications du %s au %s

""" - % (nbadded, datedebut, datefin) - ) - else: - H.append( - """

Aucune date ouvrable entre le %s et le %s !

""" - % (datedebut, datefin) - ) - - H.append( - """ -
""" - % etud - ) - H.append(sco_find_etud.form_search_etud()) - H.append(html_sco_header.sco_footer()) - return "\n".join(H) - - -def JustifAbsenceEtud(): # etudid implied - """Formulaire individuel simple de justification d'une absence""" - # brute-force portage from very old dtml code ... - etud = sco_etud.get_etud_info(filled=True)[0] - etudid = etud["etudid"] - H = [ - html_sco_header.sco_header( - page_title="Justification d'une absence pour %(nomprenom)s" % etud, - ), - """
-

Justification d'une absence pour %(nomprenom)s

-
- """ - % etud, - """""" - % url_for("scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=etudid), - sco_photos.etud_photo_html( - etudid=etudid, - title="fiche de " + etud["nomprenom"], - ), - """
""", - """ -
- - -

- - - - - - -
Date début : - -   Date Fin (optionnel):
-
- -Journée(s) - Matin(s) - Après midi - -

-Raison: (optionnel) - -

- - -

""" - % etud, - html_sco_header.sco_footer(), - ] - return "\n".join(H) - - -def doAnnuleAbsence(datedebut, datefin, demijournee, etudid=False): # etudid implied - """Annulation des absences pour une demi journée""" - etud = sco_etud.get_etud_info(filled=True, etudid=etudid)[0] - etudid = etud["etudid"] - - dates = sco_abs.DateRangeISO(datedebut, datefin) - nbadded = 0 - demijournee = int(demijournee) - for jour in dates: - if demijournee == 2: - sco_abs.annule_absence(etudid, jour, False) - sco_abs.annule_absence(etudid, jour, True) - nbadded += 2 - else: - sco_abs.annule_absence(etudid, jour, demijournee) - nbadded += 1 - # - H = [ - html_sco_header.sco_header( - page_title="Annulation d'une absence pour %(nomprenom)s" % etud, - ), - """

Annulation d'absences pour %(nomprenom)s

""" % etud, - ] - if dates: - H.append( - "

Annulation sur %d demi-journées du %s au %s" - % (nbadded, datedebut, datefin) - ) - else: - H.append( - """

Aucune date ouvrable entre le %s et le %s !

""" - % (datedebut, datefin) - ) - - H.append( - """ -
""" - % etud - ) - H.append(sco_find_etud.form_search_etud()) - H.append(html_sco_header.sco_footer()) - return "\n".join(H) - - -def AnnuleAbsenceEtud(): # etudid implied - """Formulaire individuel simple d'annulation d'une absence""" - # brute-force portage from very old dtml code ... - etud = sco_etud.get_etud_info(filled=True)[0] - etudid = etud["etudid"] - - H = [ - html_sco_header.sco_header( - page_title="Annulation d'une absence pour %(nomprenom)s" % etud, - ), - """
-

Annulation d'une absence - pour %(nomprenom)s

-
- """ - % etud, - """""" - % url_for("scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=etudid), - sco_photos.etud_photo_html( - etudid=etudid, - title="fiche de " + etud["nomprenom"], - ), - """
""", - """

A n'utiliser que suite à une erreur de saisie ou lorsqu'il s'avère que - l'étudiant était en fait présent. -

-

Si plusieurs modules sont affectés, - les absences seront toutes effacées.

- """ - % etud, - """ - -
-
- -

- - - - - - -
Date début : - j/m/a -   Date Fin (optionnel): - j/m/a -
- -journée(s) - Matin(s) - Après midi - - -

- -

-
-
- -

- - - - - - -
Date début : - j/m/a -   Date Fin (optionnel): - j/m/a -
-

- -journée(s) - Matin(s) - Après midi - - -

- -(utiliser ceci en cas de justificatif erroné saisi indépendemment -d'une absence) -

-
""" - % etud, - html_sco_header.sco_footer(), - ] - return "\n".join(H) - - -def doAnnuleJustif(datedebut0, datefin0, demijournee): # etudid implied - """Annulation d'une justification""" - etud = sco_etud.get_etud_info(filled=True)[0] - etudid = etud["etudid"] - dates = sco_abs.DateRangeISO(datedebut0, datefin0) - nbadded = 0 - demijournee = int(demijournee) - for jour in dates: - # Attention: supprime matin et après-midi - if demijournee == 2: - sco_abs.annule_justif(etudid, jour, False) - sco_abs.annule_justif(etudid, jour, True) - nbadded += 2 - else: - sco_abs.annule_justif(etudid, jour, demijournee) - nbadded += 1 - # - H = [ - html_sco_header.sco_header( - page_title="Annulation d'une justification pour %(nomprenom)s" % etud, - ), - """

Annulation de justifications pour %(nomprenom)s

""" % etud, - ] - - if dates: - H.append( - "

Annulation sur %d demi-journées du %s au %s" - % (nbadded, datedebut0, datefin0) - ) - else: - H.append( - """

Aucune date ouvrable entre le %s et le %s !

""" - % (datedebut0, datefin0) - ) - H.append( - """ -
""" - % etud - ) - H.append(sco_find_etud.form_search_etud()) - H.append(html_sco_header.sco_footer()) - return "\n".join(H) - - -def AnnuleAbsencesDatesNoJust(etudid, dates, moduleimpl_id=None): - """Supprime les absences non justifiées aux dates indiquées - Ne supprime pas les justificatifs éventuels. - Args: - etudid: l'étudiant - dates: liste de dates iso, eg [ "2000-01-15", "2000-01-16" ] - moduleimpl_id: si spécifié, n'affecte que les absences de ce module - - Returns: - None - """ - # log('AnnuleAbsencesDatesNoJust: moduleimpl_id=%s' % moduleimpl_id) - if not dates: - return - date0 = dates[0] - if len(date0.split(":")) == 2: - # am/pm is present - for date in dates: - jour, ampm = date.split(":") - if ampm == "am": - matin = 1 - elif ampm == "pm": - matin = 0 - else: - raise ValueError("invalid ampm !") - sco_abs.annule_absence(etudid, jour, matin, moduleimpl_id) - return - cnx = ndb.GetDBConnexion() - cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor) - # supr les absences non justifiees - for date in dates: - cursor.execute( - """DELETE FROM absences - WHERE etudid=%(etudid)s - AND (not estjust) - AND jour=%(date)s - AND moduleimpl_id=%(moduleimpl_id)s - """, - vars(), - ) - sco_abs.invalidate_abs_etud_date(etudid, date) - # s'assure que les justificatifs ne sont pas "absents" - for date in dates: - cursor.execute( - """UPDATE absences - SET estabs=FALSE - WHERE etudid=%(etudid)s - AND jour=%(date)s - AND moduleimpl_id=%(moduleimpl_id)s - """, - vars(), - ) - if dates: - date0 = dates[0] - else: - date0 = None - if len(dates) > 1: - date1 = dates[1] - else: - date1 = None - logdb( - cnx, - "AnnuleAbsencesDatesNoJust", - etudid=etudid, - msg="%s - %s - %s" % (date0, date1, moduleimpl_id), - ) - cnx.commit() - - -def EtatAbsences(): - """Etat des absences: choix du groupe""" - # crude portage from 1999 DTML - H = [ - html_sco_header.sco_header(page_title="Etat des absences"), - """

État des absences pour un groupe

-
""", - formChoixSemestreGroupe(), - """ - -
Date de début (j/m/a) : - - - -
Date de fin : - - - -
-
""" - % (scu.annee_scolaire(), datetime.datetime.now().strftime("%d/%m/%Y")), - html_sco_header.sco_footer(), - ] - return "\n".join(H) - - -def formChoixSemestreGroupe(all=False): - """partie de formulaire pour le choix d'un semestre et d'un groupe. - Si all, donne tous les semestres (même ceux verrouillés). - """ - # XXX assez primitif, à ameliorer TOTALEMENT OBSOLETE ! - if all: - sems = sco_formsemestre.do_formsemestre_list() - else: - sems = sco_formsemestre.do_formsemestre_list(args={"etat": "1"}) - if not sems: - raise ScoValueError("aucun semestre !") - H = ['") - return "\n".join(H) - - -def _convert_sco_year(year) -> int: - try: - year = int(year) - if year > 1900 and year < 2999: - return year - except ValueError: - raise ScoValueError("année scolaire invalide") - - -def CalAbs(etudid, sco_year=None): - """Calendrier des absences d'un etudiant""" - # crude portage from 1999 DTML - etud = sco_etud.get_etud_info(filled=True, etudid=etudid)[0] - etudid = etud["etudid"] - if sco_year: - annee_scolaire = _convert_sco_year(sco_year) - else: - annee_scolaire = scu.annee_scolaire() - datedebut = str(annee_scolaire) + "-08-01" - datefin = str(annee_scolaire + 1) + "-07-31" - annee_courante = scu.annee_scolaire() - nbabs = sco_abs.count_abs(etudid=etudid, debut=datedebut, fin=datefin) - nbabsjust = sco_abs.count_abs_just(etudid=etudid, debut=datedebut, fin=datefin) - events = [] - for a in sco_abs.list_abs_just(etudid=etudid, datedebut=datedebut): - events.append( - (str(a["jour"]), "a", "#F8B7B0", "", a["matin"], a["description"]) - ) - for a in sco_abs.list_abs_non_just(etudid=etudid, datedebut=datedebut): - events.append( - (str(a["jour"]), "A", "#EE0000", "", a["matin"], a["description"]) - ) - justifs_noabs = sco_abs.list_abs_justifs( - etudid=etudid, datedebut=datedebut, only_no_abs=True - ) - for a in justifs_noabs: - events.append( - (str(a["jour"]), "X", "#8EA2C6", "", a["matin"], a["description"]) - ) - CalHTML = sco_abs.YearTable(annee_scolaire, events=events, halfday=1) - - # - H = [ - html_sco_header.sco_header( - page_title="Calendrier des absences de %(nomprenom)s" % etud, - cssstyles=["css/calabs.css"], - ), - """ - - -

Absences de %(nomprenom)s (%(inscription)s)

""" - % etud, - """A : absence NON justifiée
- a : absence justifiée
- X : justification sans absence
- %d absences sur l'année, dont %d justifiées (soit %d non justifiées)
- (%d justificatifs inutilisés) -

- """ - % (nbabs, nbabsjust, nbabs - nbabsjust, len(justifs_noabs)), - """
%s
""" - % ( - url_for("scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=etudid), - sco_photos.etud_photo_html( - etudid=etudid, - title="fiche de " + etud["nomprenom"], - ), - ), - CalHTML, - """
""", - """""" % etudid, - """Année scolaire %s-%s""" % (annee_scolaire, annee_scolaire + 1), - """  Changer année: -
""") - H.append(html_sco_header.sco_footer()) - return "\n".join(H) - - -def ListeAbsEtud( - etudid=None, - code_nip=None, - with_evals=True, - format="html", - absjust_only=0, - sco_year=None, -): - """Liste des absences d'un étudiant sur l'année en cours - En format 'html': page avec deux tableaux (non justifiées et justifiées). - En format json, xml, xls ou pdf: l'un ou l'autre des table, suivant absjust_only. - En format 'text': texte avec liste d'absences (pour mails). - - Args: - etudid: - with_evals: indique les evaluations aux dates d'absences - absjust_only: si vrai, renvoie table absences justifiées - sco_year: année scolaire à utiliser. - Si non spécifier, utilie l'année en cours. e.g. "2005" - """ - # si absjust_only, table absjust seule (export xls ou pdf) - absjust_only = scu.to_bool(absjust_only) - if sco_year: - annee_scolaire = _convert_sco_year(sco_year) - else: - annee_scolaire = scu.annee_scolaire() - datedebut = f"{annee_scolaire}-{scu.MONTH_DEBUT_ANNEE_SCOLAIRE+1}-01" - etudid = etudid or False - etuds = sco_etud.get_etud_info(etudid=etudid, code_nip=code_nip, filled=True) - if not etuds: - log(f"ListeAbsEtud: no etuds with etudid={etudid} or nip={code_nip}") - abort(404) - etud = etuds[0] - etudid = etud["etudid"] - # Liste des absences et titres colonnes tables: - titles, columns_ids, absnonjust, absjust = _tables_abs_etud( - etudid, datedebut, with_evals=with_evals, format=format - ) - if request.base_url: - base_url_nj = "%s?etudid=%s&absjust_only=0" % (request.base_url, etudid) - base_url_j = "%s?etudid=%s&absjust_only=1" % (request.base_url, etudid) - else: - base_url_nj = base_url_j = "" - tab_absnonjust = GenTable( - titles=titles, - columns_ids=columns_ids, - rows=absnonjust, - html_class="table_leftalign", - table_id="tab_absnonjust", - base_url=base_url_nj, - filename="abs_" + scu.make_filename(etud["nomprenom"]), - caption="Absences non justifiées de %(nomprenom)s" % etud, - preferences=sco_preferences.SemPreferences(), - ) - tab_absjust = GenTable( - titles=titles, - columns_ids=columns_ids, - rows=absjust, - html_class="table_leftalign", - table_id="tab_absjust", - base_url=base_url_j, - filename="absjust_" + scu.make_filename(etud["nomprenom"]), - caption="Absences justifiées de %(nomprenom)s" % etud, - preferences=sco_preferences.SemPreferences(), - ) - - # Formats non HTML et demande d'une seule table: - if format != "html" and format != "text": - if absjust_only == 1: - return tab_absjust.make_page(format=format) - else: - return tab_absnonjust.make_page(format=format) - - if format == "html": - # Mise en forme HTML: - H = [] - H.append( - html_sco_header.sco_header(page_title="Absences de %s" % etud["nomprenom"]) - ) - H.append( - """

Absences de %s (à partir du %s)

""" - % (etud["nomprenom"], ndb.DateISOtoDMY(datedebut)) - ) - - if len(absnonjust): - H.append("

%d absences non justifiées:

" % len(absnonjust)) - H.append(tab_absnonjust.html()) - else: - H.append("""

Pas d'absences non justifiées

""") - - if len(absjust): - H.append("""

%d absences justifiées:

""" % len(absjust)) - H.append(tab_absjust.html()) - else: - H.append("""

Pas d'absences justifiées

""") - return "\n".join(H) + html_sco_header.sco_footer() - - elif format == "text": - T = [] - if not len(absnonjust) and not len(absjust): - T.append( - """--- Pas d'absences enregistrées depuis le %s""" - % ndb.DateISOtoDMY(datedebut) - ) - else: - T.append( - """--- Absences enregistrées à partir du %s:""" - % ndb.DateISOtoDMY(datedebut) - ) - T.append("\n") - if len(absnonjust): - T.append("* %d absences non justifiées:" % len(absnonjust)) - T.append(tab_absnonjust.text()) - if len(absjust): - T.append("* %d absences justifiées:" % len(absjust)) - T.append(tab_absjust.text()) - return "\n".join(T) - else: - raise ValueError("Invalid format !") - - -def _tables_abs_etud( - etudid, - datedebut, - with_evals=True, - format="html", - absjust_only=0, -): - """Tables des absences justifiees et non justifiees d'un étudiant - sur l'année en cours - """ - absjust = sco_abs.list_abs_just(etudid=etudid, datedebut=datedebut) - absnonjust = sco_abs.list_abs_non_just(etudid=etudid, datedebut=datedebut) - # examens ces jours là ? - if with_evals: - cnx = ndb.GetDBConnexion() - cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor) - for a in absnonjust + absjust: - cursor.execute( - """SELECT eval.* - FROM notes_evaluation eval, - notes_moduleimpl_inscription mi, - notes_moduleimpl m - WHERE DATE(eval.date_debut) = %(date_debut)s - and eval.moduleimpl_id = m.id - and mi.moduleimpl_id = m.id - and mi.etudid = %(etudid)s - """, - {"date_debut": a["jour"], "etudid": etudid}, - ) - a["evals"] = cursor.dictfetchall() - cursor.execute( - """SELECT mi.moduleimpl_id - FROM absences abs, notes_moduleimpl_inscription mi, notes_moduleimpl m - WHERE abs.matin = %(matin)s - and abs.jour = %(jour)s - and abs.etudid = %(etudid)s - and abs.moduleimpl_id = mi.moduleimpl_id - and mi.moduleimpl_id = m.id - and mi.etudid = %(etudid)s - """, - { - "matin": bool(a["matin"]), - "jour": a["jour"].strftime("%Y-%m-%d"), - "etudid": etudid, - }, - ) - a["absent"] = cursor.dictfetchall() - - def matin(x): - if x: - return "matin" - else: - return "après-midi" - - def descr_exams(a): - if "evals" not in a: - return "" - ex = [] - for ev in a["evals"]: - mod = sco_moduleimpl.moduleimpl_withmodule_list( - moduleimpl_id=ev["moduleimpl_id"] - )[0] - if format == "html": - ex.append( - f"""{mod["module"]["code"] or "(module sans code)"}""" - ) - else: - ex.append(mod["module"]["code"] or "(module sans code)") - if ex: - return ", ".join(ex) - return "" - - def descr_abs(a): - ex = [] - for ev in a.get("absent", []): - mod = sco_moduleimpl.moduleimpl_withmodule_list( - moduleimpl_id=ev["moduleimpl_id"] - )[0] - if format == "html": - ex.append( - f"""{mod["module"]["code"] or '(module sans code)'}""" - ) - else: - ex.append(mod["module"]["code"] or "(module sans code)") - if ex: - return ", ".join(ex) - return "" - - # ajoute date formatée et évaluations - for L in (absnonjust, absjust): - for a in L: - if with_evals: - a["exams"] = descr_exams(a) - a["datedmy"] = a["jour"].strftime("%d/%m/%Y") - a["ampm"] = int(a["matin"]) - a["matin"] = matin(a["matin"]) - index = a["description"].find(")") - if index != -1: - a["motif"] = a["description"][1:index] - else: - a["motif"] = "" - a["description"] = descr_abs(a) or "" - - # ajoute lien pour justifier - if format == "html": - for a in absnonjust: - a["justlink"] = "justifier" - a[ - "_justlink_target" - ] = "doJustifAbsence?etudid=%s&datedebut=%s&datefin=%s&demijournee=%s" % ( - etudid, - a["datedmy"], - a["datedmy"], - a["ampm"], - ) - # - titles = { - "datedmy": "Date", - "matin": "", - "exams": "Examens ce jour", - "justlink": "", - "description": "Modules", - "motif": "Motif", - } - columns_ids = ["datedmy", "matin"] - if format in ("json", "xml"): - columns_ids += ["jour", "ampm"] - if with_evals: - columns_ids.append("exams") - - columns_ids.append("description") - columns_ids.append("motif") - if format == "html": - columns_ids.append("justlink") - - return titles, columns_ids, absnonjust, absjust diff --git a/app/scodoc/sco_assiduites.py b/app/scodoc/sco_assiduites.py index f5173663..bd683033 100644 --- a/app/scodoc/sco_assiduites.py +++ b/app/scodoc/sco_assiduites.py @@ -364,7 +364,7 @@ def get_all_justified( # Gestion du cache -def get_assiduites_count(etudid, sem): +def get_assiduites_count(etudid: int, sem: dict) -> tuple[int, int]: """Les comptes d'absences de cet étudiant dans ce semestre: tuple (nb abs non justifiées, nb abs justifiées) Utilise un cache. @@ -378,25 +378,43 @@ def get_assiduites_count(etudid, sem): ) +def formsemestre_get_assiduites_count( + etudid: int, formsemestre: FormSemestre +) -> tuple[int, int]: + """Les comptes d'absences de cet étudiant dans ce semestre: + tuple (nb abs non justifiées, nb abs justifiées) + Utilise un cache. + """ + metrique = sco_preferences.get_preference("assi_metrique", formsemestre.id) + return get_assiduites_count_in_interval( + etudid, + date_debut=formsemestre.date_debut, + date_fin=formsemestre.date_fin, + metrique=scu.translate_assiduites_metric(metrique), + ) + + def get_assiduites_count_in_interval( - etudid, date_debut_iso, date_fin_iso, metrique="demi" + etudid, + date_debut_iso: str = "", + date_fin_iso: str = "", + metrique="demi", + date_debut: datetime = None, + date_fin: datetime = None, ): """Les comptes d'absences de cet étudiant entre ces deux dates, incluses: tuple (nb abs, nb abs justifiées) + On peut spécifier les dates comme datetime ou iso. Utilise un cache. """ - key = ( - str(etudid) - + "_" - + date_debut_iso - + "_" - + date_fin_iso - + f"{metrique}_assiduites" - ) + date_debut_iso = date_debut_iso or date_debut.isoformat() + date_fin_iso = date_fin_iso or date_fin.isoformat() + key = f"{etudid}_{date_debut_iso}_{date_fin_iso}{metrique}_assiduites" + r = sco_cache.AbsSemEtudCache.get(key) if not r: - date_debut: datetime.datetime = scu.is_iso_formated(date_debut_iso, True) - date_fin: datetime.datetime = scu.is_iso_formated(date_fin_iso, True) + date_debut: datetime = date_debut or datetime.fromisoformat(date_debut_iso) + date_fin: datetime = date_fin or datetime.fromisoformat(date_fin_iso) assiduites: Assiduite = Assiduite.query.filter_by(etudid=etudid) assiduites = assiduites.filter(Assiduite.etat == scu.EtatAssiduite.ABSENT) diff --git a/app/scodoc/sco_bulletins.py b/app/scodoc/sco_bulletins.py index cdd8eb76..ce5f6509 100644 --- a/app/scodoc/sco_bulletins.py +++ b/app/scodoc/sco_bulletins.py @@ -58,14 +58,12 @@ from app.scodoc.sco_exceptions import AccessDenied, ScoValueError from app.scodoc import html_sco_header from app.scodoc import htmlutils from app.scodoc import sco_assiduites -from app.scodoc import sco_abs_views from app.scodoc import sco_bulletins_generator from app.scodoc import sco_bulletins_json from app.scodoc import sco_bulletins_pdf from app.scodoc import sco_bulletins_xml from app.scodoc import codes_cursus from app.scodoc import sco_etud -from app.scodoc import sco_evaluation_db from app.scodoc import sco_formsemestre from app.scodoc import sco_groups from app.scodoc import sco_preferences @@ -1116,9 +1114,10 @@ def mail_bulletin(formsemestre_id, infos, pdfdata, filename, recipient_addr): hea = "" if sco_preferences.get_preference("bul_mail_list_abs"): - hea += "\n\n" + sco_abs_views.ListeAbsEtud( - etud["etudid"], with_evals=False, format="text" - ) + hea += "\n\n" + "(LISTE D'ABSENCES NON DISPONIBLE)" # XXX TODO-ASSIDUITE + # sco_abs_views.ListeAbsEtud( + # etud["etudid"], with_evals=False, format="text" + # ) subject = f"""Relevé de notes de {etud["nomprenom"]}""" recipients = [recipient_addr] diff --git a/app/scodoc/sco_bulletins_legacy.py b/app/scodoc/sco_bulletins_legacy.py index 37a1b202..280c6f7b 100644 --- a/app/scodoc/sco_bulletins_legacy.py +++ b/app/scodoc/sco_bulletins_legacy.py @@ -132,10 +132,13 @@ class BulletinGeneratorLegacy(sco_bulletins_generator.BulletinGenerator): if sco_preferences.get_preference( "bul_show_minmax_mod", formsemestre_id ): - rang_minmax = '%s [%s, %s]' % ( - mod["mod_rang_txt"], - scu.fmt_note(mod["stats"]["min"]), - scu.fmt_note(mod["stats"]["max"]), + rang_minmax = ( + '%s [%s, %s]' + % ( + mod["mod_rang_txt"], + scu.fmt_note(mod["stats"]["min"]), + scu.fmt_note(mod["stats"]["max"]), + ) ) else: rang_minmax = mod["mod_rang_txt"] # vide si pas option rang @@ -301,9 +304,11 @@ class BulletinGeneratorLegacy(sco_bulletins_generator.BulletinGenerator): authuser = self.authuser H = [] # --- Absences + # XXX TODO-ASSIDUITE + # au passage, utiliser url_for... H.append( """

- + XXX Absences : %(nbabs)s demi-journées, dont %(nbabsjust)s justifiées (pendant ce semestre).

diff --git a/app/scodoc/sco_bulletins_standard.py b/app/scodoc/sco_bulletins_standard.py index 4a6bb277..83b33a0e 100644 --- a/app/scodoc/sco_bulletins_standard.py +++ b/app/scodoc/sco_bulletins_standard.py @@ -124,9 +124,12 @@ class BulletinGeneratorStandard(sco_bulletins_generator.BulletinGenerator): nbabs = self.infos["nbabs"] story.append(Spacer(1, 2 * mm)) if nbabs: + # XXX TODO-ASSIDUITE + # et utiliser url_for... H.append( """

+ XXX Absences : %(nbabs)s demi-journées, dont %(nbabsjust)s justifiées (pendant ce semestre).

diff --git a/app/scodoc/sco_evaluation_check_abs.py b/app/scodoc/sco_evaluation_check_abs.py index 87853cc9..d2dea809 100644 --- a/app/scodoc/sco_evaluation_check_abs.py +++ b/app/scodoc/sco_evaluation_check_abs.py @@ -30,18 +30,16 @@ from flask import url_for, g from app import db -from app.models import Evaluation, Identite +from app.models import Evaluation, FormSemestre, Identite import app.scodoc.sco_utils as scu from app.scodoc import html_sco_header -from app.scodoc import sco_abs -from app.scodoc import sco_etud from app.scodoc import sco_evaluations from app.scodoc import sco_evaluation_db -from app.scodoc import sco_formsemestre +from app.scodoc.sco_exceptions import ScoValueError from app.scodoc import sco_groups -from app.scodoc import sco_moduleimpl +# XXX TODO-ASSIDUITE https://scodoc.org/git/ScoDoc/ScoDoc/issues/685 def evaluation_check_absences(evaluation: Evaluation): """Vérifie les absences au moment de cette évaluation. Cas incohérents que l'on peut rencontrer pour chaque étudiant: @@ -52,6 +50,8 @@ def evaluation_check_absences(evaluation: Evaluation): EXC et pas justifie Ramene 5 listes d'etudid """ + raise ScoValueError("Fonction non disponible, patience !") # XXX TODO-ASSIDUITE + if not evaluation.date_debut: return [], [], [], [], [] # evaluation sans date @@ -169,7 +169,7 @@ def evaluation_check_absences_html(evaluation_id, with_header=True, show_ok=True ) if linkabs: url = url_for( - "absences.doSignaleAbsence", + "absences.doSignaleAbsence", # XXX TODO-ASSIDUITE scodoc_dept=g.scodoc_dept, etudid=etudid, # par defaut signale le jour du début de l'éval @@ -221,7 +221,9 @@ def evaluation_check_absences_html(evaluation_id, with_header=True, show_ok=True def formsemestre_check_absences_html(formsemestre_id): """Affiche etat verification absences pour toutes les evaluations du semestre !""" - sem = sco_formsemestre.get_formsemestre(formsemestre_id) + formsemestre: FormSemestre = FormSemestre.query.filter_by( + dept_id=g.scodoc_dept_id, id=formsemestre_id + ).first_or_404() H = [ html_sco_header.html_sem_header( "Vérification absences aux évaluations de ce semestre", @@ -232,29 +234,27 @@ def formsemestre_check_absences_html(formsemestre_id):

""", ] # Modules, dans l'ordre - Mlist = sco_moduleimpl.moduleimpl_withmodule_list(formsemestre_id=formsemestre_id) - for M in Mlist: - evals = sco_evaluation_db.get_evaluation_dict( - {"moduleimpl_id": M["moduleimpl_id"]} - ) - if evals: + for modimpl in formsemestre.modimpls_sorted: + if modimpl.evaluations.count() > 0: H.append( - '

%s: %s

' - % ( - M["moduleimpl_id"], - M["module"]["code"] or "", - M["module"]["abbrev"] or "", - ) + f"""
+

{modimpl.module.code or ""}: {modimpl.module.abbrev or ""} +

""" ) - for E in evals: - H.append( - evaluation_check_absences_html( - E["evaluation_id"], - with_header=False, - show_ok=False, + for evaluation in modimpl.evaluations.order_by( + Evaluation.numero, Evaluation.date_debut + ): + H.append( + evaluation_check_absences_html( + evaluation.id, # XXX TODO-ASSIDUITE remplacer par evaluation ... + with_header=False, + show_ok=False, + ) ) - ) - if evals: H.append("
") + H.append(html_sco_header.sco_footer()) return "\n".join(H) diff --git a/app/scodoc/sco_evaluations.py b/app/scodoc/sco_evaluations.py index 4b14a632..43959272 100644 --- a/app/scodoc/sco_evaluations.py +++ b/app/scodoc/sco_evaluations.py @@ -46,8 +46,8 @@ from app.scodoc.sco_utils import ModuleType import app.scodoc.notesdb as ndb from app.scodoc.gen_tables import GenTable from app.scodoc import html_sco_header +from app.scodoc import sco_cal from app.scodoc import sco_evaluation_db -from app.scodoc import sco_abs from app.scodoc import sco_edit_module from app.scodoc import sco_edit_ue from app.scodoc import sco_formsemestre_inscriptions @@ -475,7 +475,7 @@ def formsemestre_evaluations_cal(formsemestre_id): if color == color_futur: event[2] = color_futur - cal_html = sco_abs.YearTable( + cal_html = sco_cal.YearTable( year, events=list(events.values()), halfday=False, pad_width=None ) diff --git a/app/scodoc/sco_formsemestre_status.py b/app/scodoc/sco_formsemestre_status.py index d375a47d..45c8b4e7 100755 --- a/app/scodoc/sco_formsemestre_status.py +++ b/app/scodoc/sco_formsemestre_status.py @@ -888,13 +888,14 @@ def _make_listes_sem(formsemestre: FormSemestre, with_absences=True): if n_members == 0: continue # skip empty groups partition_is_empty = False - group["url_etat"] = url_for( - "absences.EtatAbsencesGr", - group_ids=group["group_id"], - debut=formsemestre.date_debut.strftime("%d/%m/%Y"), - fin=formsemestre.date_fin.strftime("%d/%m/%Y"), - scodoc_dept=g.scodoc_dept, - ) + # XXX TODO-ASSIDUITE + group["url_etat"] = "non disponible" # url_for( + # "absences.EtatAbsencesGr", + # group_ids=group["group_id"], + # debut=formsemestre.date_debut.strftime("%d/%m/%Y"), + # fin=formsemestre.date_fin.strftime("%d/%m/%Y"), + # scodoc_dept=g.scodoc_dept, + # ) if group["group_name"]: group["label"] = "groupe %(group_name)s" % group else: diff --git a/app/scodoc/sco_groups_view.py b/app/scodoc/sco_groups_view.py index 6b534f0e..5f12735d 100644 --- a/app/scodoc/sco_groups_view.py +++ b/app/scodoc/sco_groups_view.py @@ -45,7 +45,7 @@ from app import db from app.models import FormSemestre import app.scodoc.sco_utils as scu from app.scodoc import html_sco_header -from app.scodoc import sco_abs +from app.scodoc import sco_cal from app.scodoc import sco_excel from app.scodoc import sco_formsemestre from app.scodoc import sco_groups @@ -829,7 +829,8 @@ def tab_absences_html(groups_infos, etat=None): "
  • ", form_choix_jour_saisie_hebdo(groups_infos), "
  • ", - """
  • État des absences du groupe
  • """ + # XXX TODO-ASSIDUITE + """
  • XXX État des absences du groupe
  • """ % ( groups_infos.groups_query_args, groups_infos.formsemestre["date_debut"], @@ -890,12 +891,13 @@ def form_choix_jour_saisie_hebdo(groups_infos, moduleimpl_id=None): if not authuser.has_permission(Permission.ScoAbsChange): return "" sem = groups_infos.formsemestre - first_monday = sco_abs.ddmmyyyy(sem["date_debut"]).prev_monday() + first_monday = sco_cal.ddmmyyyy(sem["date_debut"]).prev_monday() today_idx = datetime.date.today().weekday() FA = [] # formulaire avec menu saisi absences FA.append( - '
    ' + # TODO-ASSIDUITE et utiliser url_for... (was Absences/SignaleAbsenceGrSemestre) + '' ) FA.append('' % sem) FA.append(groups_infos.get_form_elem()) @@ -906,12 +908,12 @@ def form_choix_jour_saisie_hebdo(groups_infos, moduleimpl_id=None): FA.append('') FA.append( - """""" + """""" ) FA.append("""' % datelundi) FA.append('' % moduleimpl_id) FA.append('' % destination) FA.append(groups_infos.get_form_elem()) - FA.append('') + FA.append( + '' + ) # XXX FA.append("
    ") return "\n".join(FA) diff --git a/app/scodoc/sco_moduleimpl.py b/app/scodoc/sco_moduleimpl.py index 64ea3eb0..415c442d 100644 --- a/app/scodoc/sco_moduleimpl.py +++ b/app/scodoc/sco_moduleimpl.py @@ -230,7 +230,7 @@ def do_moduleimpl_inscription_list(moduleimpl_id=None, etudid=None): return _moduleimpl_inscriptionEditor.list(cnx, args) -def moduleimpl_listeetuds(moduleimpl_id): +def moduleimpl_listeetuds(moduleimpl_id): # XXX OBSOLETE "retourne liste des etudids inscrits a ce module" req = """SELECT DISTINCT Im.etudid FROM notes_moduleimpl_inscription Im, diff --git a/app/scodoc/sco_moduleimpl_status.py b/app/scodoc/sco_moduleimpl_status.py index af8a90e8..3cfb4cd8 100644 --- a/app/scodoc/sco_moduleimpl_status.py +++ b/app/scodoc/sco_moduleimpl_status.py @@ -47,7 +47,7 @@ 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_cal from app.scodoc import sco_compute_moy from app.scodoc import sco_evaluations from app.scodoc import sco_evaluation_db @@ -332,10 +332,6 @@ def moduleimpl_status(moduleimpl_id=None, partition_id=None): '' # 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""" - Saisie Absences hebdo. + Saisie Absences hebdo. (INDISPONIBLE) """ ) + # TODO-ASSIDUITE + # href="{ + # url_for("absences.SignaleAbsenceGrHebdo", + # scodoc_dept=g.scodoc_dept,formsemestre_id=formsemestre_id, + # moduleimpl_id=moduleimpl_id, datelundi=datelundi, group_ids=group_id) + # }" H.append("") # diff --git a/app/scodoc/sco_saisie_notes.py b/app/scodoc/sco_saisie_notes.py index d814c3c6..a2c95148 100644 --- a/app/scodoc/sco_saisie_notes.py +++ b/app/scodoc/sco_saisie_notes.py @@ -45,7 +45,6 @@ from app.models import ( FormSemestre, Module, ModuleImpl, - NotesNotes, ScolarNews, ) from app.models.etudiants import Identite @@ -54,16 +53,13 @@ from app.scodoc.sco_exceptions import ( AccessDenied, InvalidNoteValue, NoteProcessError, - ScoBugCatcher, ScoException, ScoInvalidParamError, ScoValueError, ) from app.scodoc import html_sco_header, sco_users from app.scodoc import htmlutils -from app.scodoc import sco_abs from app.scodoc import sco_cache -from app.scodoc import sco_edit_module from app.scodoc import sco_etud from app.scodoc import sco_evaluation_db from app.scodoc import sco_evaluations @@ -71,7 +67,6 @@ from app.scodoc import sco_excel from app.scodoc import sco_formsemestre_inscriptions from app.scodoc import sco_groups from app.scodoc import sco_groups_view -from app.scodoc import sco_moduleimpl from app.scodoc import sco_permissions_check from app.scodoc import sco_undo_notes import app.scodoc.notesdb as ndb @@ -1113,16 +1108,16 @@ def _get_sorted_etuds(evaluation: Evaluation, etudids: list, formsemestre_id: in ) warn_abs_lst = [] if evaluation.is_matin(): - nbabs = sco_abs.count_abs(etudid, jour_iso, jour_iso, matin=True) - nbabsjust = sco_abs.count_abs_just(etudid, jour_iso, jour_iso, matin=True) + nbabs = 0 # TODO-ASSIDUITE sco_abs.count_abs(etudid, jour_iso, jour_iso, matin=True) + nbabsjust = 0 # TODO-ASSIDUITE sco_abs.count_abs_just(etudid, jour_iso, jour_iso, matin=True) if nbabs: if nbabsjust: warn_abs_lst.append("absent justifié le matin !") else: warn_abs_lst.append("absent le matin !") if evaluation.is_apresmidi(): - nbabs = sco_abs.count_abs(etudid, jour_iso, jour_iso, matin=0) - nbabsjust = sco_abs.count_abs_just(etudid, jour_iso, jour_iso, matin=0) + nbabs = 0 # TODO-ASSIDUITE sco_abs.count_abs(etudid, jour_iso, jour_iso, matin=0) + nbabsjust = 0 # TODO-ASSIDUITE sco_abs.count_abs_just(etudid, jour_iso, jour_iso, matin=0) if nbabs: if nbabsjust: warn_abs_lst.append("absent justifié l'après-midi !") diff --git a/app/scodoc/sco_trombino_tours.py b/app/scodoc/sco_trombino_tours.py index 6d017a8f..821c0e9e 100644 --- a/app/scodoc/sco_trombino_tours.py +++ b/app/scodoc/sco_trombino_tours.py @@ -41,7 +41,7 @@ from reportlab.lib.units import cm from reportlab.platypus import KeepInFrame, Paragraph, Table, TableStyle from reportlab.platypus.doctemplate import BaseDocTemplate -from app.scodoc import sco_abs +from app.scodoc import sco_cal from app.scodoc import sco_etud from app.scodoc.sco_exceptions import ScoPDFFormatError from app.scodoc import sco_groups @@ -299,9 +299,9 @@ def pdf_feuille_releve_absences( NB_CELL_PM = sco_preferences.get_preference("feuille_releve_abs_PM") col_width = 0.85 * cm if sco_preferences.get_preference("feuille_releve_abs_samedi"): - days = sco_abs.DAYNAMES[:6] # Lundi, ..., Samedi + days = sco_cal.DAYNAMES[:6] # Lundi, ..., Samedi else: - days = sco_abs.DAYNAMES[:5] # Lundi, ..., Vendredi + days = sco_cal.DAYNAMES[:5] # Lundi, ..., Vendredi nb_days = len(days) # Informations sur les groupes à afficher: diff --git a/app/static/js/abs_ajax.js b/app/static/js/abs_ajax.js deleted file mode 100644 index 4f67294f..00000000 --- a/app/static/js/abs_ajax.js +++ /dev/null @@ -1,47 +0,0 @@ - -// JS Ajax code for SignaleAbsenceGrSemestre -// Contributed by YLB - -function ajaxFunction(mod, etudid, dat) { - var ajaxRequest; // The variable that makes Ajax possible! - - try { - // Opera 8.0+, Firefox, Safari - ajaxRequest = new XMLHttpRequest(); - } catch (e) { - // Internet Explorer Browsers - try { - ajaxRequest = new ActiveXObject("Msxml2.XMLHTTP"); - } catch (e) { - try { - ajaxRequest = new ActiveXObject("Microsoft.XMLHTTP"); - } catch (e) { - // Something went wrong - alert("Your browser broke!"); - return false; - } - } - } - // Create a function that will receive data sent from the server - ajaxRequest.onreadystatechange = function () { - if (ajaxRequest.readyState == 4 && ajaxRequest.status == 200) { - document.getElementById("AjaxDiv").innerHTML = ajaxRequest.responseText; - } - } - ajaxRequest.open("POST", SCO_URL + "/Absences/doSignaleAbsenceGrSemestre", true); - ajaxRequest.setRequestHeader("Content-type", "application/x-www-form-urlencoded"); - var oSelectOne = $("#abs_form")[0].elements["moduleimpl_id"]; - var index = oSelectOne.selectedIndex; - var modul_id = oSelectOne.options[index].value; - if (mod == 'add') { - ajaxRequest.send("reply=0&moduleimpl_id=" + modul_id + "&abslist:list=" + etudid + ":" + dat); - } - if (mod == 'remove') { - ajaxRequest.send("reply=0&moduleimpl_id=" + modul_id + "&etudids=" + etudid + "&dates=" + dat); - } -} - -// ----- -function change_moduleimpl(url) { - document.location = url + '&moduleimpl_id=' + document.getElementById('moduleimpl_id').value; -} diff --git a/app/views/absences.py b/app/views/absences.py index 181c8f05..3268bb4c 100644 --- a/app/views/absences.py +++ b/app/views/absences.py @@ -24,36 +24,12 @@ ############################################################################## """ -Module absences: issu de ScoDoc7 / ZAbsences.py - -Emmanuel Viennet, 2021 - -Gestion des absences (v4) - -Code dérivé de la partie la plus ancienne de ScoDoc, et à revoir. - -L'API de plus bas niveau est en gros: - - AnnuleAbsencesDatesNoJust( dates) - count_abs(etudid, debut, fin, matin=None, moduleimpl_id=None) - count_abs_just(etudid, debut, fin, matin=None, moduleimpl_id=None) - list_abs_just(etudid, datedebut) [pas de fin ?] - list_abs_non_just(etudid, datedebut) [pas de fin ?] - list_abs_justifs(etudid, datedebut, datefin=None, only_no_abs=True) - - list_abs_jour(date, am=True, pm=True, is_abs=None, is_just=None) - list_abs_non_just_jour(date, am=True, pm=True) +Module absences: remplacé par assiduité en août 2023, reste ici seulement la gestion des "billets" """ -import calendar -import datetime import dateutil import dateutil.parser -import re -import time -import urllib -from xml.etree import ElementTree import flask from flask import g, request @@ -62,50 +38,27 @@ from flask_login import current_user from app import db, log from app import api -from app.comp import res_sem -from app.comp.res_compat import NotesTableCompat + from app.decorators import ( scodoc, scodoc7func, permission_required, permission_required_compat_scodoc7, ) -from app.models import FormSemestre, GroupDescr, Partition from app.models.absences import BilletAbsence from app.models.etudiants import Identite from app.views import absences_bp as bp # --------------- from app.scodoc import sco_utils as scu -from app.scodoc import notesdb as ndb -from app.scodoc.scolog import logdb from app.scodoc.sco_permissions import Permission -from app.scodoc.sco_exceptions import ScoValueError, APIInvalidParams +from app.scodoc.sco_exceptions import ScoValueError from app.scodoc.TrivialFormulator import TrivialFormulator -from app.scodoc.gen_tables import GenTable from app.scodoc import html_sco_header -from app.scodoc import sco_abs +from app.scodoc import sco_cal from app.scodoc import sco_abs_billets -from app.scodoc import sco_abs_views from app.scodoc import sco_etud -from app.scodoc import sco_find_etud -from app.scodoc import sco_formsemestre -from app.scodoc import sco_groups_view -from app.scodoc import sco_moduleimpl from app.scodoc import sco_preferences -from app.scodoc import sco_xml - - -CSSSTYLES = html_sco_header.BOOTSTRAP_MULTISELECT_CSS - - -def sco_publish(route, function, permission, methods=["GET"]): - """Declare a route for a python function, - protected by permission and called following ScoDoc 7 Zope standards. - """ - return bp.route(route, methods=methods)( - scodoc(permission_required(permission)(scodoc7func(function))) - ) # -------------------------------------------------------------------- @@ -125,22 +78,9 @@ def index_html(): H = [ html_sco_header.sco_header( - page_title="Saisie des absences", - cssstyles=["css/calabs.css"], - javascripts=["js/calabs.js"], + page_title="Billets d'absences", ), - """

    Traitement des absences

    -

    - Pour saisir des absences ou consulter les états, il est recommandé par passer par - le semestre concerné (saisie par jours nommés ou par semaines). -

    - """, ] - H.append( - """

    Pour signaler, annuler ou justifier une absence pour un seul étudiant, - choisissez d'abord concerné:

    """ - ) - H.append(sco_find_etud.form_search_etud()) if current_user.has_permission( Permission.ScoAbsChange ) and sco_preferences.get_preference("handle_billets_abs"): @@ -156,961 +96,6 @@ def index_html(): return "\n".join(H) -@bp.route("/choix_semaine") -@scodoc -@permission_required(Permission.ScoAbsChange) -@scodoc7func -def choix_semaine(group_id): - """Page choix semaine sur calendrier pour saisie absences d'un groupe""" - group = ( - GroupDescr.query.filter_by(id=group_id) - .join(Partition) - .join(FormSemestre) - .filter_by(dept_id=g.scodoc_dept_id) - .first_or_404() - ) - H = [ - html_sco_header.sco_header( - page_title="Saisie des absences", - cssstyles=["css/calabs.css"], - javascripts=["js/calabs.js"], - ), - f""" -

    Saisie des Absences

    -
    -

    - - Saisie par semaine - Groupe: {group.get_nom_with_part()} - - -

    - """, - cal_select_week(), - """

    Sélectionner le groupe d'étudiants, puis cliquez sur une semaine pour - saisir les absences de toute cette semaine.

    -
    - """, - html_sco_header.sco_footer(), - ] - return "\n".join(H) - - -def cal_select_week(year=None): - "display calendar allowing week selection" - if not year: - year = scu.annee_scolaire() - sems = sco_formsemestre.do_formsemestre_list() - if not sems: - js = "" - else: - js = 'onmouseover="highlightweek(this);" onmouseout="deselectweeks();" onclick="wclick(this);"' - C = sco_abs.YearTable(int(year), dayattributes=js) - return C - - -sco_publish("/EtatAbsences", sco_abs_views.EtatAbsences, Permission.ScoView) -sco_publish("/CalAbs", sco_abs_views.CalAbs, Permission.ScoView) -sco_publish( - "/SignaleAbsenceEtud", - sco_abs_views.SignaleAbsenceEtud, - Permission.ScoAbsChange, - methods=["GET", "POST"], -) -sco_publish( - "/doSignaleAbsence", - sco_abs_views.doSignaleAbsence, - Permission.ScoAbsChange, - methods=["GET", "POST"], -) -sco_publish( - "/JustifAbsenceEtud", - sco_abs_views.JustifAbsenceEtud, - Permission.ScoAbsChange, - methods=["GET", "POST"], -) -sco_publish( - "/doJustifAbsence", - sco_abs_views.doJustifAbsence, - Permission.ScoAbsChange, - methods=["GET", "POST"], -) -sco_publish( - "/AnnuleAbsenceEtud", - sco_abs_views.AnnuleAbsenceEtud, - Permission.ScoAbsChange, - methods=["GET", "POST"], -) -sco_publish( - "/doAnnuleAbsence", - sco_abs_views.doAnnuleAbsence, - Permission.ScoAbsChange, - methods=["GET", "POST"], -) -sco_publish( - "/doAnnuleJustif", - sco_abs_views.doAnnuleJustif, - Permission.ScoAbsChange, - methods=["GET", "POST"], -) -sco_publish( - "/AnnuleAbsencesDatesNoJust", - sco_abs_views.AnnuleAbsencesDatesNoJust, - Permission.ScoAbsChange, - methods=["GET", "POST"], -) - - -# Antédiluvienne fonction: #deprecated -@bp.route("/ListeAbsEtud", methods=["GET", "POST"]) # pour compat anciens clients PHP -@scodoc -@permission_required_compat_scodoc7(Permission.ScoView) -@scodoc7func -def ListeAbsEtud( - etudid=None, - code_nip=None, - with_evals=True, - format="html", - absjust_only=0, - sco_year=None, -): - return sco_abs_views.ListeAbsEtud( - etudid=etudid, - code_nip=str(code_nip), - with_evals=with_evals, - format=format, - absjust_only=absjust_only, - sco_year=sco_year, - ) - - -# -------------------------------------------------------------------- -# -# SQL METHODS (xxx #sco8 not views => à déplacer) -# -# -------------------------------------------------------------------- - -# API backward compatibility -sco_publish("/CountAbs", sco_abs.count_abs, Permission.ScoView) -sco_publish("/CountAbsJust", sco_abs.count_abs_just, Permission.ScoView) -# TODO nouvel appel rendnat les deux valeurs et utilisant le cache - - -@bp.route("/doSignaleAbsenceGrSemestre", methods=["GET", "POST"]) -@scodoc -@permission_required(Permission.ScoAbsChange) -@scodoc7func -def doSignaleAbsenceGrSemestre( - moduleimpl_id=None, - abslist=[], - dates="", - etudids="", - destination=None, -): - """Enregistre absences aux dates indiquees (abslist et dates). - dates est une liste de dates ISO (séparées par des ','). - Efface les absences aux dates indiquées par dates, - ou bien ajoute celles de abslist. - """ - moduleimpl_id = moduleimpl_id or None - if etudids: - etudids = [int(x) for x in str(etudids).split(",")] - else: - etudids = [] - if dates: - dates = dates.split(",") - else: - dates = [] - - # 1- Efface les absences - if dates: - for etudid in etudids: - sco_abs_views.AnnuleAbsencesDatesNoJust(etudid, dates, moduleimpl_id) - return "Absences effacées" - - # 2- Ajoute les absences - if abslist: - sco_abs.add_abslist(abslist, moduleimpl_id) - return "Absences ajoutées" - - return ("", 204) - - -# ------------ HTML Interfaces -@bp.route("/SignaleAbsenceGrHebdo", methods=["GET", "POST"]) -@scodoc -@permission_required(Permission.ScoAbsChange) -@scodoc7func -def SignaleAbsenceGrHebdo( - datelundi, group_ids=[], destination="", moduleimpl_id=None, formsemestre_id=None -): - "Saisie hebdomadaire des absences" - if not moduleimpl_id: - moduleimpl_id = None - - groups_infos = sco_groups_view.DisplayedGroupsInfos( - group_ids, moduleimpl_id=moduleimpl_id, formsemestre_id=formsemestre_id - ) - if not groups_infos.members: - return ( - html_sco_header.sco_header(page_title="Saisie des absences") - + "

    Aucun étudiant !

    " - + html_sco_header.sco_footer() - ) - - base_url = "SignaleAbsenceGrHebdo?datelundi=%s&%s&destination=%s" % ( - datelundi, - groups_infos.groups_query_args, - urllib.parse.quote(destination), - ) - - formsemestre_id = groups_infos.formsemestre_id - formsemestre = FormSemestre.get_formsemestre(formsemestre_id) - if formsemestre.dept_id != g.scodoc_dept_id: - abort(404, "groupes inexistants dans ce département") - require_module = sco_preferences.get_preference( - "abs_require_module", formsemestre_id - ) - etuds = [ - sco_etud.get_etud_info(etudid=m["etudid"], filled=True)[0] - for m in groups_infos.members - ] - # Restreint aux inscrits au module sélectionné - if moduleimpl_id: - mod_inscrits = set( - [ - x["etudid"] - for x in sco_moduleimpl.do_moduleimpl_inscription_list( - moduleimpl_id=moduleimpl_id - ) - ] - ) - etuds_inscrits_module = [e for e in etuds if e["etudid"] in mod_inscrits] - if etuds_inscrits_module: - etuds = etuds_inscrits_module - else: - # Si aucun etudiant n'est inscrit au module choisi... - moduleimpl_id = None - - nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre) - sem = formsemestre.to_dict() - - # calcule dates jours de cette semaine - # liste de dates iso "yyyy-mm-dd" - datessem = [ndb.DateDMYtoISO(datelundi)] - for _ in sco_abs.day_names()[1:]: - datessem.append(sco_abs.next_iso_day(datessem[-1])) - # - if groups_infos.tous_les_etuds_du_sem: - gr_tit = "en" - else: - if len(groups_infos.group_ids) > 1: - p = "des groupes" - else: - p = "du groupe" - gr_tit = p + ' ' + groups_infos.groups_titles + "" - - H = [ - html_sco_header.sco_header( - page_title="Saisie hebdomadaire des absences", - init_qtip=True, - javascripts=html_sco_header.BOOTSTRAP_MULTISELECT_JS - + [ - "js/etud_info.js", - "js/abs_ajax.js", - "js/groups_view.js", - ], - cssstyles=CSSSTYLES, - no_side_bar=1, - ), - """
    -

    Saisie des absences %s %s, - semaine du lundi %s

    -
    -
    - - - - - Groupes: %s -
    -
    - """ - % ( - gr_tit, - sem["titre_num"], - datelundi, - groups_infos.formsemestre_id, - datelundi, - destination, - moduleimpl_id or "", - sco_groups_view.menu_groups_choice(groups_infos, submit_on_change=True), - ), - ] - # - modimpls_list = [] - ues = nt.get_ues_stat_dict() - for ue in ues: - modimpls_list += nt.get_modimpls_dict(ue_id=ue["ue_id"]) - - menu_module = "" - for modimpl in modimpls_list: - if modimpl["moduleimpl_id"] == moduleimpl_id: - sel = "selected" - else: - sel = "" - menu_module += ( - """\n""" - % { - "modimpl_id": modimpl["moduleimpl_id"], - "modname": (modimpl["module"]["code"] or "") - + " " - + (modimpl["module"]["abbrev"] or modimpl["module"]["titre"] or ""), - "sel": sel, - } - ) - if moduleimpl_id: - sel = "" - else: - sel = "selected" # aucun module specifie - - H.append( - """Module concerné: - -
    """ - % {"menu_module": menu_module, "url": base_url, "sel": sel} - ) - - H += _gen_form_saisie_groupe( - etuds, datessem, destination, moduleimpl_id, require_module - ) - - H.append(html_sco_header.sco_footer()) - return "\n".join(H) - - -@bp.route("/SignaleAbsenceGrSemestre", methods=["GET", "POST"]) -@scodoc -@permission_required(Permission.ScoAbsChange) -@scodoc7func -def SignaleAbsenceGrSemestre( - datedebut, - datefin, - destination="", - group_ids=(), # list of groups to display - nbweeks=4, # ne montre que les nbweeks dernieres semaines - moduleimpl_id=None, -): - """Saisie des absences sur une journée sur un semestre (ou intervalle de dates) entier""" - groups_infos = sco_groups_view.DisplayedGroupsInfos(group_ids) - if not groups_infos.members: - return ( - html_sco_header.sco_header(page_title="Saisie des absences") - + "

    Aucun étudiant !

    " - + html_sco_header.sco_footer() - ) - formsemestre_id = groups_infos.formsemestre_id - formsemestre: FormSemestre = FormSemestre.query.get_or_404(formsemestre_id) - if formsemestre.dept_id != g.scodoc_dept_id: - return abort(404, "groupes inexistants dans ce département") - sem = formsemestre.to_dict() - require_module = sco_preferences.get_preference( - "abs_require_module", formsemestre_id - ) - etuds = [ - sco_etud.get_etud_info(etudid=m["etudid"], filled=True)[0] - for m in groups_infos.members - ] - # Restreint aux inscrits au module sélectionné - if moduleimpl_id: - mod_inscrits = set( - [ - x["etudid"] - for x in sco_moduleimpl.do_moduleimpl_inscription_list( - moduleimpl_id=moduleimpl_id - ) - ] - ) - etuds = [e for e in etuds if e["etudid"] in mod_inscrits] - if not moduleimpl_id: - moduleimpl_id = None - base_url_noweeks = ( - "SignaleAbsenceGrSemestre?datedebut=%s&datefin=%s&%s&destination=%s" - % ( - datedebut, - datefin, - groups_infos.groups_query_args, - urllib.parse.quote(destination), - ) - ) - base_url = base_url_noweeks + "&nbweeks=%s" % nbweeks # sans le moduleimpl_id - - if etuds: - nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre) - - work_saturday = sco_abs.is_work_saturday() - jourdebut = sco_abs.ddmmyyyy(datedebut, work_saturday=work_saturday) - jourfin = sco_abs.ddmmyyyy(datefin, work_saturday=work_saturday) - today = sco_abs.ddmmyyyy( - time.strftime("%d/%m/%Y", time.localtime()), - work_saturday=work_saturday, - ) - today.next_day() - if jourfin > today: # ne propose jamais les semaines dans le futur - jourfin = today - if jourdebut > today: - raise ScoValueError("date de début dans le futur (%s) !" % jourdebut) - # - if not jourdebut.iswork() or jourdebut > jourfin: - raise ValueError( - "date debut invalide (%s, ouvrable=%d)" - % (str(jourdebut), jourdebut.iswork()) - ) - # calcule dates - dates = [] # sco_abs.ddmmyyyy instances - d = sco_abs.ddmmyyyy(datedebut, work_saturday=work_saturday) - while d <= jourfin: - dates.append(d) - d = d.next_day(7) # avance d'une semaine - # - msg = "Montrer seulement les 4 dernières semaines" - nwl = 4 - if nbweeks: - nbweeks = int(nbweeks) - if nbweeks > 0: - dates = dates[-nbweeks:] - msg = "Montrer toutes les semaines" - nwl = 0 - url_link_semaines = base_url_noweeks + "&nbweeks=%s" % nwl - if moduleimpl_id: - url_link_semaines += "&moduleimpl_id=" + str(moduleimpl_id) - # - dates = [x.ISO() for x in dates] - day_name = sco_abs.day_names()[jourdebut.weekday] - - if groups_infos.tous_les_etuds_du_sem: - gr_tit = "en" - else: - if len(groups_infos.group_ids) > 1: - p = "des groupes " - else: - p = "du groupe " - gr_tit = p + '' + groups_infos.groups_titles + "" - - H = [ - html_sco_header.sco_header( - page_title=f"Saisie des absences du {day_name}", - init_qtip=True, - javascripts=["js/etud_info.js", "js/abs_ajax.js"], - no_side_bar=1, - ), - f"""
    -

    Saisie des absences {gr_tit} {sem["titre_num"]}, - les {day_name}s

    -

    - {msg} - - """, - ] - # - if etuds: - modimpls_list = [] - ues = nt.get_ues_stat_dict() - for ue in ues: - modimpls_list += nt.get_modimpls_dict(ue_id=ue["ue_id"]) - - menu_module = "" - for modimpl in modimpls_list: - if modimpl["moduleimpl_id"] == moduleimpl_id: - sel = "selected" - else: - sel = "" - menu_module += ( - """\n""" - % { - "modimpl_id": modimpl["moduleimpl_id"], - "modname": (modimpl["module"]["code"] or "") - + " " - + (modimpl["module"]["abbrev"] or modimpl["module"]["titre"] or ""), - "sel": sel, - } - ) - if moduleimpl_id: - sel = "" - else: - sel = "selected" # aucun module specifie - H.append( - """

    -Module concerné par ces absences (%(optionel_txt)s): - -

    """ - % { - "menu_module": menu_module, - "url": base_url, - "sel": sel, - "optionel_txt": 'requis' - if require_module - else "optionnel", - } - ) - - H += _gen_form_saisie_groupe( - etuds, dates, destination, moduleimpl_id, require_module - ) - H.append(html_sco_header.sco_footer()) - return "\n".join(H) - - -def _gen_form_saisie_groupe( - etuds, dates, destination="", moduleimpl_id=None, require_module=False -): - """Formulaire saisie absences - - Args: - etuds: liste des étudiants - dates: liste ordonnée de dates iso, par exemple: [ '2020-12-24', ... ] - moduleimpl_id: optionnel, module concerné. - """ - H = [ - f""" - -
    -
    - - - """ - ] - # Dates - odates = [datetime.date(*[int(x) for x in d.split("-")]) for d in dates] - begin = dates[0] - end = dates[-1] - # Titres colonnes - noms_jours = [] # eg [ "Lundi", "mardi", "Samedi", ... ] - jn = sco_abs.day_names() - for d in odates: - idx_jour = d.weekday() - if 0 <= idx_jour < len(jn): - noms_jours.append(jn[idx_jour]) - else: - noms_jours.append("???") # jour non travaillé - for jour in noms_jours: - H.append( - f"""""" - ) - H.append("") - for d in odates: - H.append( - f"""""" - ) - H.append("") - H.append("" * len(dates)) - H.append("") - # - if not etuds: - H.append( - '' - ) - i = 1 - cnx = ndb.GetDBConnexion() - cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor) - for etud in etuds: - i += 1 - etudid = etud["etudid"] - etud_class = "etudinfo" # css - # UE capitalisee dans semestre courant ? - cap = [] - if etud["cursem"]: - formsemestre = FormSemestre.query.get_or_404( - etud["cursem"]["formsemestre_id"] - ) - nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre) - for ue in nt.get_ues_stat_dict(): - ue_status = nt.get_etud_ue_status(etudid, ue["ue_id"]) - if ue_status and ue_status["is_capitalized"]: - cap.append(ue["acronyme"]) - if cap: - capstr = ' (%s cap.)' % ", ".join(cap) - else: - capstr = "" - if etud["etatincursem"] == "D": - capstr += ' (dém.)' - etud_class += " etuddem" - tr_class = ("row_1", "row_2", "row_3")[i % 3] - td_matin_class = ("matin_1", "matin_2", "matin_3")[i % 3] - - H.append( - f""" - """ - ) - etud_abs = sco_abs.list_abs_in_range( - etudid, begin, end, moduleimpl_id=moduleimpl_id, cursor=cursor - ) - for d in odates: - date = d.strftime("%Y-%m-%d") - # matin - is_abs = {"jour": d, "matin": True} in etud_abs - if is_abs: - checked = "checked" - else: - checked = "" - # bulle lors du passage souris - coljour = sco_abs.DAYNAMES[(calendar.weekday(d.year, d.month, d.day))] - datecol = coljour + " " + d.strftime("%d/%m/%Y") - bulle_am = '"' + etud["nomprenom"] + " - " + datecol + ' (matin)"' - bulle_pm = '"' + etud["nomprenom"] + " - " + datecol + ' (ap.midi)"' - - H.append( - '' - % ( - td_matin_class, - bulle_am, - str(etudid) + ":" + date + ":" + "am", - checked, - etudid, - date + ":am", - ) - ) - # après-midi - is_abs = {"jour": d, "matin": False} in etud_abs - if is_abs: - checked = "checked" - else: - checked = "" - H.append( - '' - % ( - bulle_pm, - str(etudid) + ":" + date + ":" + "pm", - checked, - etudid, - date + ":pm", - ) - ) - H.append("") - H.append("
    {len(etuds)} étudiants - { jour } -
      - { d.strftime("%d/%m/%Y") } -
     AMPM
    Aucun étudiant inscrit !
    - { - etud["nomprenom"]}{capstr} -
    ") - # place la liste des etudiants et les dates pour pouvoir effacer les absences - H.append( - '' - % ",".join([str(etud["etudid"]) for etud in etuds]) - ) - H.append('' % dates[0]) - H.append('' % dates[-1]) - H.append('' % ",".join(dates)) - H.append( - '' - % urllib.parse.quote(destination) - ) - # - # version pour formulaire avec AJAX (Yann LB) - H.append( - """ -

    - -

    -
    -

    Les cases cochées correspondent à des absences. - Les absences saisies ne sont pas justifiées (sauf si un justificatif a été entré - par ailleurs). -

    Si vous "décochez" une case, l'absence correspondante sera supprimée. - Attention, les modifications sont automatiquement entregistrées au fur et à mesure. -

    - """ - ) - return H - - -@bp.route("/EtatAbsencesGr") -@scodoc -@permission_required(Permission.ScoView) -@scodoc7func # ported from dtml -def EtatAbsencesGr( - group_ids=[], # list of groups to display - debut="", - fin="", - with_boursier=True, # colonne boursier - format="html", -): - """Liste les absences de groupes""" - datedebut = ndb.DateDMYtoISO(debut) - datefin = ndb.DateDMYtoISO(fin) - # Informations sur les groupes à afficher: - groups_infos = sco_groups_view.DisplayedGroupsInfos(group_ids) - formsemestre_id = groups_infos.formsemestre_id - sem = groups_infos.formsemestre - - # Construit tableau (etudid, statut, nomprenom, nbJust, nbNonJust, NbTotal) - T = [] - for m in groups_infos.members: - etud = sco_etud.get_etud_info(etudid=m["etudid"], filled=True)[0] - nbabs = sco_abs.count_abs(etudid=etud["etudid"], debut=datedebut, fin=datefin) - nbabsjust = sco_abs.count_abs_just( - etudid=etud["etudid"], debut=datedebut, fin=datefin - ) - nbjustifs_noabs = len( - sco_abs.list_abs_justifs( - etudid=etud["etudid"], - datedebut=datedebut, - datefin=datefin, - only_no_abs=True, - ) - ) - # retrouve sem dans etud['sems'] - s = None - for s in etud["sems"]: - if s["formsemestre_id"] == formsemestre_id: - break - if not s or s["formsemestre_id"] != formsemestre_id: - raise ValueError( - "EtatAbsencesGr: can't retreive sem" - ) # bug or malicious arg - T.append( - { - "etudid": etud["etudid"], - "etatincursem": s["ins"]["etat"], - "nomprenom": etud["nomprenom"], - "nbabsjust": nbabsjust, - "nbabsnonjust": nbabs - nbabsjust, - "nbabs": nbabs, - "nbjustifs_noabs": nbjustifs_noabs, - "_nomprenom_target": "CalAbs?etudid=%s" % etud["etudid"], - "_nomprenom_td_attrs": 'id="%s" class="etudinfo"' % etud["etudid"], - "boursier": "oui" if etud["boursier"] else "non", - } - ) - if s["ins"]["etat"] == "D": - T[-1]["_css_row_class"] = "etuddem" - T[-1]["nomprenom"] += " (dem)" - columns_ids = [ - "nomprenom", - "nbjustifs_noabs", - "nbabsjust", - "nbabsnonjust", - "nbabs", - ] - if with_boursier: - columns_ids[1:1] = ["boursier"] - if groups_infos.tous_les_etuds_du_sem: - gr_tit = "" - else: - if len(groups_infos.group_ids) > 1: - p = "des groupes" - else: - p = "du groupe" - if format == "html": - h = ' ' + groups_infos.groups_titles + "" - else: - h = groups_infos.groups_titles - gr_tit = p + h - - title = f"État des absences {gr_tit}" - if format == "xls" or format == "xml" or format == "json": - columns_ids = ["etudid"] + columns_ids - # --- Formulaire choix dates début / fin - form_date = ( - f""" -
    - - Période du - -  au  - - -   - (nombre de demi-journées) -
    """ - + """ - - """ - ) - tab = GenTable( - columns_ids=columns_ids, - rows=T, - preferences=sco_preferences.SemPreferences(formsemestre_id), - titles={ - "etatincursem": "Etat", - "nomprenom": "Nom", - "nbabsjust": "Justifiées", - "nbabsnonjust": "Non justifiées", - "nbabs": "Total", - "nbjustifs_noabs": "Justifs non utilisés", - "boursier": "Bourse", - }, - html_sortable=True, - html_class="table_leftalign", - html_header=html_sco_header.sco_header( - page_title=title, - init_qtip=True, - javascripts=["js/etud_info.js"], - ), - html_title=html_sco_header.html_sem_header(title, with_page_header=False) - + form_date, - # "

    Période du %s au %s (nombre de demi-journées)
    " % (debut, fin), - base_url="%s&formsemestre_id=%s&debut=%s&fin=%s" - % (groups_infos.base_url, formsemestre_id, debut, fin), - filename="etat_abs_" - + scu.make_filename( - "%s de %s" % (groups_infos.groups_filename, sem["titreannee"]) - ), - caption=title, - html_next_section="""

    -

    -Justifs non utilisés: nombre de demi-journées avec justificatif mais sans absences relevées. -

    -

    -Cliquez sur un nom pour afficher le calendrier des absences
    -ou entrez une date pour visualiser les absents un jour donné : -

    -
    -
    - -%s - - -
    - """ - % (request.base_url, formsemestre_id, groups_infos.get_form_elem()), - ) - return tab.make_page(format=format) - - -@bp.route("/EtatAbsencesDate") -@scodoc -@permission_required(Permission.ScoView) -@scodoc7func -def EtatAbsencesDate(group_ids=[], date=None): # list of groups to display - # ported from dtml - """Etat des absences pour un groupe à une date donnée""" - # Informations sur les groupes à afficher: - groups_infos = sco_groups_view.DisplayedGroupsInfos(group_ids) - H = [html_sco_header.sco_header(page_title="Etat des absences")] - if date: - dateiso = ndb.DateDMYtoISO(date) - nbetud = 0 - t_nbabsjustam = 0 - t_nbabsam = 0 - t_nbabsjustpm = 0 - t_nbabspm = 0 - H.append(f"

    État des absences le {date}

    ") - H.append( - """ - - - - - - """ - ) - for etud in groups_infos.members: - nbabsam = sco_abs.count_abs( - etudid=etud["etudid"], debut=dateiso, fin=dateiso, matin=1 - ) - nbabspm = sco_abs.count_abs( - etudid=etud["etudid"], debut=dateiso, fin=dateiso, matin=0 - ) - if (nbabsam != 0) or (nbabspm != 0): - nbetud += 1 - nbabsjustam = sco_abs.count_abs_just( - etudid=etud["etudid"], debut=dateiso, fin=dateiso, matin=1 - ) - nbabsjustpm = sco_abs.count_abs_just( - etudid=etud["etudid"], debut=dateiso, fin=dateiso, matin=0 - ) - H.append( - f""" - - ") - H.append( - f""" - - - - -
     MatinAprès-midi
    - {etud["nomprenom"]} - - """ - ) - if nbabsam != 0: - if nbabsjustam: - H.append("Just.") - t_nbabsjustam += 1 - else: - H.append("Abs.") - t_nbabsam += 1 - else: - H.append("") - H.append('') - if nbabspm != 0: - if nbabsjustpm: - H.append("Just.") - t_nbabsjustpm += 1 - else: - H.append("Abs.") - t_nbabspm += 1 - else: - H.append("") - H.append("
    {t_nbabsam} abs, {t_nbabsjustam} just.{t_nbabspm} abs, {t_nbabsjustpm} just.
    - """ - ) - if nbetud == 0: - H.append("

    Aucune absence !

    ") - else: - H.append( - """

    Erreur: vous n'avez pas choisi de date !

    - """ - ) - - return "\n".join(H) + html_sco_header.sco_footer() - - # ----- Gestion des "billets d'absence": signalement par les etudiants eux mêmes (à travers le portail) @bp.route("/AddBilletAbsence", methods=["GET", "POST"]) # API ScoDoc 7 compat @scodoc @@ -1329,9 +314,11 @@ def _ProcessBilletAbsence( # 1-- Ajout des absences (et justifs) datedebut = billet.abs_begin.strftime("%d/%m/%Y") datefin = billet.abs_end.strftime("%d/%m/%Y") - dates = sco_abs.DateRangeISO(datedebut, datefin) + dates = sco_cal.DateRangeISO(datedebut, datefin) # commence après-midi ? if dates and billet.abs_begin.hour > 11: + # XXX TODO-ASSIDUITE + raise ScoValueError("Fonction non disponible") sco_abs.add_absence( billet.etudid, dates[0], @@ -1343,6 +330,8 @@ def _ProcessBilletAbsence( dates = dates[1:] # termine matin ? if dates and billet.abs_end.hour < 12: + # XXX TODO-ASSIDUITE + raise ScoValueError("Fonction non disponible") sco_abs.add_absence( billet.etudid, dates[-1], @@ -1354,6 +343,7 @@ def _ProcessBilletAbsence( dates = dates[:-1] for jour in dates: + raise ScoValueError("Fonction non disponible") sco_abs.add_absence( billet.etudid, jour, @@ -1361,6 +351,7 @@ def _ProcessBilletAbsence( estjust, description=description, ) + # XXX TODO-ASSIDUITE sco_abs.add_absence( billet.etudid, jour, @@ -1482,41 +473,3 @@ def process_billet_absence_form(billet_id: int): tab = sco_abs_billets.table_billets(billets, etud=etud) H.append(tab.html()) return "\n".join(H) + html_sco_header.sco_footer() - - -@bp.route("/XMLgetAbsEtud", methods=["GET", "POST"]) # pour compat anciens clients PHP -@scodoc -@permission_required_compat_scodoc7(Permission.ScoView) -@scodoc7func -def XMLgetAbsEtud(beg_date="", end_date=""): - """returns list of absences in date interval""" - t0 = time.time() - etuds = sco_etud.get_etud_info(filled=False) - if not etuds: - raise APIInvalidParams("étudiant inconnu") - etud = etuds[0] - exp = re.compile(r"^(\d{4})\D?(0[1-9]|1[0-2])\D?([12]\d|0[1-9]|3[01])$") - if not exp.match(beg_date): - raise ScoValueError("invalid date: %s" % beg_date) - if not exp.match(end_date): - raise ScoValueError("invalid date: %s" % end_date) - - abs_list = sco_abs.list_abs_date(etud["etudid"], beg_date, end_date) - - doc = ElementTree.Element( - "absences", etudid=str(etud["etudid"]), beg_date=beg_date, end_date=end_date - ) - for a in abs_list: - if a["estabs"]: # ne donne pas les justifications si pas d'absence - doc.append( - ElementTree.Element( - "abs", - begin=a["begin"], - end=a["end"], - description=a["description"], - justified=str(int(a["estjust"])), - ) - ) - log("XMLgetAbsEtud (%gs)" % (time.time() - t0)) - data = sco_xml.XML_HEADER + ElementTree.tostring(doc).decode(scu.SCO_ENCODING) - return scu.send_file(data, mime=scu.XML_MIMETYPE, attached=False) diff --git a/app/views/assiduites.py b/app/views/assiduites.py index 7e8bcd42..d4608dc5 100644 --- a/app/views/assiduites.py +++ b/app/views/assiduites.py @@ -547,7 +547,6 @@ def signal_assiduites_group(): + [ # Voir fonctionnement JS "js/etud_info.js", - "js/abs_ajax.js", "js/groups_view.js", "js/assiduites.js", "libjs/moment.new.min.js", diff --git a/app/views/notes.py b/app/views/notes.py index 7bd1be45..0ef19368 100644 --- a/app/views/notes.py +++ b/app/views/notes.py @@ -97,9 +97,9 @@ from app.scodoc.sco_exceptions import ( ) from app.scodoc import html_sco_header from app.pe import pe_view -from app.scodoc import sco_abs from app.scodoc import sco_apogee_compare from app.scodoc import sco_archives +from app.scodoc import sco_assiduites from app.scodoc import sco_bulletins from app.scodoc import sco_bulletins_pdf from app.scodoc import sco_cache @@ -1152,175 +1152,64 @@ def edit_moduleimpl_resp(moduleimpl_id: int): ) -_EXPR_HELP = """

    Expérimental: formule de calcul de la moyenne %(target)s

    -

    Attention: l'utilisation de formules ralentit considérablement - les traitements. A utiliser uniquement dans les cas ne pouvant pas être traités autrement.

    -

    Dans la formule, les variables suivantes sont définies:

    -
      -
    • moy la moyenne, calculée selon la règle standard (moyenne pondérée)
    • -
    • moy_is_valid vrai si la moyenne est valide (numérique)
    • -
    • moy_val la valeur de la moyenne (nombre, valant 0 si invalide)
    • -
    • notes vecteur des notes (/20) aux %(objs)s
    • -
    • coefs vecteur des coefficients des %(objs)s, les coefs des %(objs)s sans notes (ATT, EXC) étant mis à zéro
    • -
    • cmask vecteur de 0/1, 0 si le coef correspondant a été annulé
    • -
    • Nombre d'absences: nb_abs, nb_abs_just, nb_abs_nojust (en demi-journées)
    • -
    -

    Les éléments des vecteurs sont ordonnés dans l'ordre des %(objs)s%(ordre)s.

    -

    Les fonctions suivantes sont utilisables: abs, cmp, dot, len, map, max, min, pow, reduce, round, sum, ifelse.

    -

    La notation V(1,2,3) représente un vecteur (1,2,3).

    -

    Pour indiquer que la note calculée n'existe pas, utiliser la chaîne 'NA'.

    -

    Vous pouvez désactiver la formule (et revenir au mode de calcul "classique") - en supprimant le texte ou en faisant précéder la première ligne par #

    -""" - - -@bp.route("/edit_moduleimpl_expr", methods=["GET", "POST"]) -@scodoc -@permission_required(Permission.ScoView) -@scodoc7func -def edit_moduleimpl_expr(moduleimpl_id): - """Edition formule calcul moyenne module - Accessible par Admin, dir des etud et responsable module - - Inutilisé en ScoDoc 9. - """ - M, sem = sco_moduleimpl.can_change_ens(moduleimpl_id) - H = [ - html_sco_header.html_sem_header( - 'Modification règle de calcul du module %s' - % (moduleimpl_id, M["module"]["titre"]), - ), - _EXPR_HELP - % { - "target": "du module", - "objs": "évaluations", - "ordre": " (le premier élément est la plus ancienne évaluation)", - }, - ] - initvalues = M - form = [ - ("moduleimpl_id", {"input_type": "hidden"}), - ( - "computation_expr", - { - "title": "Formule de calcul", - "input_type": "textarea", - "rows": 4, - "cols": 60, - "explanation": "formule de calcul (expérimental)", - }, - ), - ] - tf = TrivialFormulator( - request.base_url, - scu.get_request_args(), - form, - submitlabel="Modifier formule de calcul", - cancelbutton="Annuler", - initvalues=initvalues, - ) - if tf[0] == 0: - return "\n".join(H) + tf[1] + html_sco_header.sco_footer() - elif tf[0] == -1: - return flask.redirect( - url_for( - "notes.moduleimpl_status", - scodoc_dept=g.scodoc_dept, - moduleimpl_id=moduleimpl_id, - ) - ) - else: - sco_moduleimpl.do_moduleimpl_edit( - { - "moduleimpl_id": moduleimpl_id, - "computation_expr": tf[2]["computation_expr"], - }, - formsemestre_id=sem["formsemestre_id"], - ) - sco_cache.invalidate_formsemestre( - formsemestre_id=sem["formsemestre_id"] - ) # > modif regle calcul - flash("règle de calcul modifiée") - return flask.redirect( - url_for( - "notes.moduleimpl_status", - scodoc_dept=g.scodoc_dept, - moduleimpl_id=moduleimpl_id, - ) - ) - - -@bp.route("/delete_moduleimpl_expr", methods=["GET", "POST"]) -@scodoc -@permission_required(Permission.ScoView) -@scodoc7func -def delete_moduleimpl_expr(moduleimpl_id): - """Suppression formule calcul moyenne module - Accessible par Admin, dir des etud et responsable module - """ - modimpl = ModuleImpl.query.get_or_404(moduleimpl_id) - sco_moduleimpl.can_change_ens(moduleimpl_id) - modimpl.computation_expr = None - db.session.add(modimpl) - db.session.commit() - flash("Ancienne formule supprimée") - return flask.redirect( - url_for( - "notes.moduleimpl_status", - scodoc_dept=g.scodoc_dept, - moduleimpl_id=moduleimpl_id, - ) - ) - - @bp.route("/view_module_abs") @scodoc @permission_required(Permission.ScoView) @scodoc7func -def view_module_abs(moduleimpl_id, format="html"): +def view_module_abs(moduleimpl_id, fmt="html"): """Visualisation des absences a un module""" - M = sco_moduleimpl.moduleimpl_withmodule_list(moduleimpl_id=moduleimpl_id)[0] - sem = sco_formsemestre.get_formsemestre(M["formsemestre_id"]) - debut_sem = ndb.DateDMYtoISO(sem["date_debut"]) - fin_sem = ndb.DateDMYtoISO(sem["date_fin"]) - list_insc = sco_moduleimpl.moduleimpl_listeetuds(moduleimpl_id) + modimpl: ModuleImpl = ( + ModuleImpl.query.filter_by(id=moduleimpl_id) + .join(FormSemestre) + .filter_by(dept_id=g.scodoc_dept_id) + ).first_or_404() - T = [] - for etudid in list_insc: - nb_abs = sco_abs.count_abs( - etudid=etudid, - debut=debut_sem, - fin=fin_sem, - moduleimpl_id=moduleimpl_id, + debut_sem = modimpl.formsemestre.date_debut + fin_sem = modimpl.formsemestre.date_fin + inscrits: list[Identite] = sorted( + [i.etud for i in modimpl.inscriptions], key=lambda e: e.sort_key + ) + + rows = [] + for etud in inscrits: + # TODO-ASSIDUITE ne va pas car ne filtre pas sur le moduleimpl + # nb_abs, nb_abs_just = sco_assiduites.formsemestre_get_assiduites_count(etud.id, modimpl.formsemestre) + nb_abs, nb_abs_just = 0, 0 # XXX TODO-ASSIDUITE + # nb_abs = sco_abs.count_abs( + # etudid=etud.id, + # debut=debut_sem, + # fin=fin_sem, + # moduleimpl_id=moduleimpl_id, + # ) + # if nb_abs: + # nb_abs_just = sco_abs.count_abs_just( + # etudid=etud.id, + # debut=debut_sem, + # fin=fin_sem, + # moduleimpl_id=moduleimpl_id, + # ) + rows.append( + { + "nomprenom": etud.nomprenom, + "just": nb_abs_just, + "nojust": nb_abs - nb_abs_just, + "total": nb_abs, + "_nomprenom_target": url_for( + "scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=etud.id + ), + } ) - if nb_abs: - nb_abs_just = sco_abs.count_abs_just( - etudid=etudid, - debut=debut_sem, - fin=fin_sem, - moduleimpl_id=moduleimpl_id, - ) - etud = sco_etud.get_etud_info(etudid=etudid, filled=True)[0] - T.append( - { - "nomprenom": etud["nomprenom"], - "just": nb_abs_just, - "nojust": nb_abs - nb_abs_just, - "total": nb_abs, - "_nomprenom_target": url_for( - "scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=etudid - ), - } - ) H = [ html_sco_header.html_sem_header( - 'Absences du module %s' - % (moduleimpl_id, M["module"]["titre"]), - page_title="Absences du module %s" % (M["module"]["titre"]), + f"""Absences du module {modimpl.module.titre_str()}""", + page_title=f"Absences du module {modimpl.module.titre_str()}", ) ] - if not T and format == "html": + if not rows and fmt == "html": return ( "\n".join(H) + "

    Aucune absence signalée

    " @@ -1335,16 +1224,16 @@ def view_module_abs(moduleimpl_id, format="html"): "total": "Total", }, columns_ids=("nomprenom", "just", "nojust", "total"), - rows=T, + rows=rows, html_class="table_leftalign", - base_url="%s?moduleimpl_id=%s" % (request.base_url, moduleimpl_id), - filename="absmodule_" + scu.make_filename(M["module"]["titre"]), - caption="Absences dans le module %s" % M["module"]["titre"], + base_url=f"{request.base_url}?moduleimpl_id={moduleimpl_id}", + filename="absmodule_" + scu.make_filename(modimpl.module.titre_str()), + caption=f"Absences dans le module {modimpl.module.titre_str()}", preferences=sco_preferences.SemPreferences(), ) - if format != "html": - return tab.make_page(format=format) + if fmt != "html": + return tab.make_page(format=fmt) return "\n".join(H) + tab.html() + html_sco_header.sco_footer() diff --git a/tests/unit/test_abs_counts.py b/tests/unit/test_abs_counts.py deleted file mode 100644 index 9f1147bb..00000000 --- a/tests/unit/test_abs_counts.py +++ /dev/null @@ -1,117 +0,0 @@ -# -*- mode: python -*- -# -*- coding: utf-8 -*- - -""" -Comptage des absences -""" -# test écrit par Fares Amer, mai 2021 et porté sur ScoDoc 8 en juillet 2021 - -from tests.unit import sco_fake_gen - -from app.scodoc import sco_abs, sco_formsemestre -from app.scodoc import sco_abs_views - - -def test_abs_counts(test_client): - """Comptage des absences""" - G = sco_fake_gen.ScoFake(verbose=False) - - # --- Création d'étudiants - etud = G.create_etud(code_nip=None) - - # --- Création d'une formation - formation_id = G.create_formation(acronyme="") - ue_id = G.create_ue(formation_id=formation_id, acronyme="TST1", titre="ue test") - matiere_id = G.create_matiere(ue_id=ue_id, titre="matière test") - module_id = G.create_module( - matiere_id=matiere_id, - code="TSM1", - coefficient=1.0, - titre="module test", - ) - - # --- Mise place d'un semestre - formsemestre_id = G.create_formsemestre( - formation_id=formation_id, - semestre_id=1, - date_debut="01/01/2021", - date_fin="30/06/2021", - ) - sem = sco_formsemestre.get_formsemestre(formsemestre_id) - _ = G.create_moduleimpl( - module_id=module_id, - formsemestre_id=formsemestre_id, - ) - - # --- Inscription des étudiants - G.inscrit_etudiant(formsemestre_id, etud) - - # --- Saisie absences - etudid = etud["etudid"] - - for debut, fin, demijournee in [ - ("01/01/2020", "31/01/2020", 2), # hors semestre - ("15/01/2021", "15/01/2021", 1), - ("18/01/2021", "18/01/2021", 0), - ("19/01/2021", "19/01/2021", 2), - ("22/01/2021", "22/01/2021", 1), - ("30/06/2021", "30/06/2021", 2), # dernier jour - ]: - sco_abs_views.doSignaleAbsence( - datedebut=debut, - datefin=fin, - demijournee=demijournee, - etudid=etudid, - ) - - # --- Justification de certaines absences - - for debut, fin, demijournee in [ - ("15/01/2021", "15/01/2021", 1), - ("18/01/2021", "18/01/2021", 0), - ("19/01/2021", "19/01/2021", 2), - ]: - sco_abs_views.doJustifAbsence( - datedebut=debut, - datefin=fin, - demijournee=demijournee, - etudid=etudid, - ) - - # --- Utilisation de get_abs_count() de sco_abs - - nbabs, nbabsjust = sco_abs.get_abs_count(etudid, sem) - - # --- Utilisation de sco_abs.count_abs() - - nb_abs2 = sco_abs.count_abs(etudid=etudid, debut="2021-01-01", fin="2021-06-30") - nb_absj2 = sco_abs.count_abs_just( - etudid=etudid, debut="2021-01-01", fin="2021-06-30" - ) - - assert nbabs == nb_abs2 == 7 - assert nbabsjust == nb_absj2 == 4 - - # --- Nombre de justificatifs: - justifs = sco_abs.list_abs_justifs(etudid, "2021-01-01", datefin="2021-06-30") - assert len(justifs) == 4 - - # --- Suppression d'absence - _ = sco_abs_views.doAnnuleAbsence("19/01/2021", "19/01/2021", 2, etudid=etudid) - - # --- Vérification - justifs_2 = sco_abs.list_abs_justifs(etudid, "2021-01-01", datefin="2021-06-30") - assert len(justifs_2) == len(justifs) - new_nbabs, _ = sco_abs.get_abs_count(etudid, sem) # version cachée - new_nbabs2 = sco_abs.count_abs(etudid=etudid, debut="2021-01-01", fin="2021-06-30") - - assert new_nbabs == new_nbabs2 - assert new_nbabs == (nbabs - 2) # on a supprimé deux absences - - # --- annulation absence sans supprimer le justificatif - sco_abs_views.AnnuleAbsencesDatesNoJust(etudid, ["2021-01-15"]) - nbabs_3, nbjust_3 = sco_abs.get_abs_count(etudid, sem) - assert nbabs_3 == new_nbabs - justifs_3 = sco_abs.list_abs_justifs(etudid, "2021-01-01", datefin="2021-06-30") - assert len(justifs_3) == len(justifs_2) - # XXX à continuer diff --git a/tests/unit/test_abs_demijournee.py b/tests/unit/test_abs_demijournee.py deleted file mode 100644 index 0a54a7ec..00000000 --- a/tests/unit/test_abs_demijournee.py +++ /dev/null @@ -1,344 +0,0 @@ -# -*- mode: python -*- -# -*- coding: utf-8 -*- - -""" -Créer et justifier des absences en utilisant le parametre demijournee -""" -# test écrit par Fares Amer, mai 2021 et porté sur ScoDoc 8 en juillet 2021 -import datetime -import json - -from tests.unit import sco_fake_gen -from app import db -from app.models import Module -from app.scodoc import sco_abs -from app.scodoc import sco_abs_views -from app.scodoc import sco_groups -from app.scodoc import sco_formsemestre -from app.scodoc import sco_preferences -from app.views import absences - - -def test_abs_demijournee(test_client): - """Opération élémentaires sur les absences, tests demi-journées - Travaille dans base TEST00 (defaut) - """ - G = sco_fake_gen.ScoFake(verbose=False) - - # --- Création d'étudiants - etud = G.create_etud(code_nip=None) - - # --- Création d'une formation - formation_id = G.create_formation(acronyme="") - ue_id = G.create_ue(formation_id=formation_id, acronyme="TST1", titre="ue test") - matiere_id = G.create_matiere(ue_id=ue_id, titre="matière test") - module_id = G.create_module( - matiere_id=matiere_id, - code="TSM1", - coefficient=1.0, - titre="module test", - ) - - # --- Mise place d'un semestre - formsemestre_id = G.create_formsemestre( - formation_id=formation_id, - semestre_id=1, - date_debut="01/01/2021", - date_fin="30/06/2021", - ) - sem = sco_formsemestre.get_formsemestre(formsemestre_id) - - _ = G.create_moduleimpl( - module_id=module_id, - formsemestre_id=formsemestre_id, - ) - - # --- Inscription des étudiants - G.inscrit_etudiant(formsemestre_id, etud) - - # --- Saisie absences - etudid = etud["etudid"] - - _ = sco_abs_views.doSignaleAbsence( - "15/01/2021", - "15/01/2021", - demijournee=2, - etudid=etudid, - ) - - _ = sco_abs_views.doSignaleAbsence( - "18/01/2021", - "18/01/2021", - demijournee=1, - etudid=etudid, - ) - - _ = sco_abs_views.doSignaleAbsence( - "19/01/2021", - "19/01/2021", - demijournee=0, - etudid=etudid, - ) - - # --- Justification de certaines absences - - _ = sco_abs_views.doJustifAbsence( - "18/01/2021", - "18/01/2021", - demijournee=1, - etudid=etudid, - ) - - _ = sco_abs_views.doJustifAbsence( - "19/01/2021", - "19/01/2021", - demijournee=2, - etudid=etudid, - ) - - # NE JUSTIFIE QUE LE MATIN MALGRES LE PARAMETRE demijournee = 2 - - # --- Test - - nbabs, nbabs_just = sco_abs.get_abs_count(etudid, sem) - assert ( - nbabs == 4 - ) # l'étudiant a été absent le 15 journée compléte (2 abs : 1 matin, 1 apres midi) et le 18 (1 matin), et le 19 (1 apres midi). - assert nbabs_just == 2 # Justifie abs du matin + abs après midi - - -def test_abs_basic(test_client): - """creation de 10 étudiants, formation, semestre, ue, module, absences le matin, l'apres midi, la journée compléte - et justification d'absences, supression d'absences, création d'une liste etat absences, creation d'un groupe afin - de tester la fonction EtatAbsencesGroupes - - Fonctions de l'API utilisé : - - doSignaleAbsence - - doAnnuleAbsence - - doJustifAbsence - - get_partition_groups - - get_partitions_list - - sco_abs.get_abs_count(etudid, sem) - - ListeAbsEtud - - partition_create - - create_group - - set_group - - EtatAbsencesGr - - AddBilletAbsence - - billets_etud - """ - G = sco_fake_gen.ScoFake(verbose=False) - - # --- Création d'étudiants - etuds = [G.create_etud(code_nip=None) for _ in range(10)] - - # --- Création d'une formation - formation_id = G.create_formation(acronyme="") - ue_id = G.create_ue(formation_id=formation_id, acronyme="TST1", titre="ue test") - matiere_id = G.create_matiere(ue_id=ue_id, titre="matière test") - module_id = G.create_module( - matiere_id=matiere_id, - code="TSM1", - coefficient=1.0, - titre="module test", - ) - - # --- Mise place d'un semestre - formsemestre_id = G.create_formsemestre( - formation_id=formation_id, - semestre_id=1, - date_debut="01/01/2021", - date_fin="30/06/2021", - ) - - moduleimpl_id = G.create_moduleimpl( - module_id=module_id, - formsemestre_id=formsemestre_id, - ) - - # --- Inscription des étudiants - for etud in etuds: - G.inscrit_etudiant(formsemestre_id, etud) - - # --- Création d'une évaluation - e = G.create_evaluation( - moduleimpl_id=moduleimpl_id, - date_debut=datetime.datetime(2021, 1, 22), - description="evaluation test", - coefficient=1.0, - ) - - # --- Saisie absences - etudid = etuds[0]["etudid"] - - _ = sco_abs_views.doSignaleAbsence( - "15/01/2021", - "15/01/2021", - demijournee=1, - etudid=etudid, - ) - - _ = sco_abs_views.doSignaleAbsence( - "18/01/2021", - "18/01/2021", - demijournee=0, - etudid=etudid, - ) - - _ = sco_abs_views.doSignaleAbsence( - "19/01/2021", - "19/01/2021", - demijournee=2, - etudid=etudid, - ) - - _ = sco_abs_views.doSignaleAbsence( - "22/01/2021", - "22/01/2021", - demijournee=1, - etudid=etudid, - ) - - # --- Justification de certaines absences - - _ = sco_abs_views.doJustifAbsence( - "15/01/2021", - "15/01/2021", - demijournee=1, - etudid=etudid, - ) - - _ = sco_abs_views.doJustifAbsence( - "18/01/2021", - "18/01/2021", - demijournee=0, - etudid=etudid, - ) - - _ = sco_abs_views.doJustifAbsence( - "19/01/2021", - "19/01/2021", - demijournee=2, - etudid=etudid, - ) - - # --- Test - - b = sco_abs.is_work_saturday() - assert b == 0 # samedi ne sont pas compris - sem = sco_formsemestre.get_formsemestre(formsemestre_id) - nbabs, nbabsjust = sco_abs.get_abs_count(etudid, sem) - # l'étudiant a été absent le 15 (apres midi) , (16 et 17 we), - # 18 (matin) et 19 janvier (matin et apres midi), et 22 (matin) - assert nbabs == 5 - # l'étudiant justifie ses abs du 15, 18 et 19 - assert nbabsjust == 4 - - # --- Suppression d'une absence et d'une justification - - _ = sco_abs_views.doAnnuleAbsence("19/01/2021", "19/01/2021", 2, etudid=etudid) - nbabs, nbabsjust = sco_abs.get_abs_count(etudid, sem) - assert nbabs == 3 - assert nbabsjust == 2 - - # --- suppression d'une justification pas encore disponible à l'aide de python. - - # --- Création d'une liste d'abs - - liste_abs = sco_abs_views.ListeAbsEtud( - etudid, format="json", absjust_only=1, sco_year="2020" - ).get_data(as_text=True) - liste_abs2 = sco_abs_views.ListeAbsEtud( - etudid, format="json", sco_year="2020" - ).get_data(as_text=True) - - load_liste_abs = json.loads(liste_abs) - load_liste_abs2 = json.loads(liste_abs2) - - assert len(load_liste_abs2) == 1 - assert len(load_liste_abs) == 2 - assert load_liste_abs2[0]["ampm"] == 1 - assert load_liste_abs2[0]["datedmy"] == "22/01/2021" - mod = db.session.get(Module, module_id) - assert load_liste_abs2[0]["exams"] == mod.code - # absjust_only -> seulement les abs justifiés - - # --- Création d'un groupe - - _ = sco_groups.partition_create( - formsemestre_id=sem["formsemestre_id"], - partition_name="Eleve", - ) - li1 = sco_groups.get_partitions_list(sem["formsemestre_id"]) - _ = sco_groups.create_group(li1[0]["partition_id"], "Groupe 1") - - # --- Affectation des élèves dans des groupes - - li_grp1 = sco_groups.get_partition_groups(li1[0]) - for etud in etuds: - sco_groups.set_group(etud["etudid"], li_grp1[0]["group_id"]) - - # --- Test de EtatAbsencesGroupes - - grp1_abs = absences.EtatAbsencesGr( - group_ids=[li_grp1[0]["group_id"]], - debut="01/01/2021", - fin="30/06/2021", - format="json", - ) - # grp1_abs est une Response car on a appelé une vue (1er appel) - load_grp1_abs = json.loads(grp1_abs.get_data(as_text=True)) - - assert len(load_grp1_abs) == 10 - - tab_id = [] # tab des id present dans load_grp1_abs - for un_etud in load_grp1_abs: - tab_id.append(un_etud["etudid"]) - - for ( - etud - ) in ( - etuds - ): # verification si tous les etudiants sont present dans la liste du groupe d'absence - assert etud["etudid"] in tab_id - - for un_etud in load_grp1_abs: - if un_etud["etudid"] == etudid: - assert un_etud["nbabs"] == 3 - assert un_etud["nbjustifs_noabs"] == 2 - assert un_etud["nbabsjust"] == 2 - assert un_etud["nbabsnonjust"] == 1 - assert un_etud["nomprenom"] == etuds[0]["nomprenom"] - - # --- Création de billets - # Active la gestion de billets: - sco_preferences.get_base_preferences().set(None, "handle_billets_abs", 1) - - b1 = absences.AddBilletAbsence( - begin="2021-01-22 00:00", - end="2021-01-22 23:59", - etudid=etudid, - description="abs du 22", - justified=False, - code_nip=etuds[0]["code_nip"], - code_ine=etuds[0]["code_ine"], - ) - - b2 = absences.AddBilletAbsence( - begin="2021-01-15 00:00", - end="2021-01-15 23:59", - etudid=etudid, - description="abs du 15", - code_nip=etuds[0]["code_nip"], - code_ine=etuds[0]["code_ine"], - ) - - li_bi = absences.billets_etud(etudid=etudid, format="json").get_data(as_text=True) - assert isinstance(li_bi, str) - load_li_bi = json.loads(li_bi) - - assert len(load_li_bi) == 2 - assert ( - load_li_bi[1]["description"] == "abs du 15" - or load_li_bi[1]["description"] == "abs du 22" - ) diff --git a/tests/unit/test_assiduites.py b/tests/unit/test_assiduites.py index a9657278..093d6cf4 100644 --- a/tests/unit/test_assiduites.py +++ b/tests/unit/test_assiduites.py @@ -12,7 +12,9 @@ import app.scodoc.sco_assiduites as scass import app.scodoc.sco_utils as scu from app import db from app.models import Assiduite, FormSemestre, Identite, Justificatif, ModuleImpl -from app.scodoc import sco_abs_views, sco_formsemestre + +# from app.scodoc import sco_abs_views, sco_formsemestre TODO-ASSIDUITE + from app.scodoc.sco_exceptions import ScoValueError from tests.unit import sco_fake_gen from tools import downgrade_module, migrate_abs_to_assiduites @@ -36,6 +38,7 @@ def test_bi_directional_enum(test_client): assert BiInt.inverse()[1] == BiInt.A and BiInt.inverse()[2] == BiInt.B +@pytest.mark.skip # XXX TODO-ASSIDUITE (issue #690) def test_general(test_client): """tests général du modèle assiduite""" @@ -76,7 +79,9 @@ def test_general(test_client): date_fin="31/07/2024", ) - formsemestre_1 = sco_formsemestre.get_formsemestre(formsemestre_id_1) + formsemestre_1 = sco_formsemestre.get_formsemestre( + formsemestre_id_1 + ) # Utiliser plutot FormSemestre de nos jours TODO-ASSIDUITE formsemestre_2 = sco_formsemestre.get_formsemestre(formsemestre_id_2) formsemestre_3 = sco_formsemestre.get_formsemestre(formsemestre_id_3) @@ -143,6 +148,7 @@ def test_general(test_client): editer_supprimer_justificatif(etuds[0]) +@pytest.mark.skip # XXX TODO-ASSIDUITE (issue #696) def verif_migration_abs_assiduites(): """Vérification que le script de migration fonctionne correctement""" downgrade_module(assiduites=True, justificatifs=True) @@ -301,7 +307,7 @@ def verif_migration_abs_assiduites(): False, ), # 3 assi 22-23-24/02/2023 08h > 13h (3dj) JUSTI(ext) ]: - sco_abs_views.doSignaleAbsence( + sco_abs_views.doSignaleAbsence( # TODO-ASSIDUITE datedebut=debut, datefin=fin, demijournee=demijournee, diff --git a/tests/unit/test_sco_basic.py b/tests/unit/test_sco_basic.py index 49eb7c24..030207e5 100644 --- a/tests/unit/test_sco_basic.py +++ b/tests/unit/test_sco_basic.py @@ -25,8 +25,6 @@ from app.comp import res_sem from app.comp.res_compat import NotesTableCompat from app.models import FormSemestre from app.scodoc import sco_formsemestre -from app.scodoc import sco_abs -from app.scodoc import sco_abs_views from app.scodoc import sco_bulletins from app.scodoc import codes_cursus from app.scodoc import sco_evaluations @@ -188,20 +186,21 @@ def run_sco_basic(verbose=False) -> FormSemestre: # ----------------------- etudid = etuds[0]["etudid"] - _ = sco_abs_views.doSignaleAbsence( - "15/01/2020", "18/01/2020", demijournee=2, etudid=etudid - ) + # XXX TODO-ASSIDUITE + # _ = sco_abs_views.doSignaleAbsence( + # "15/01/2020", "18/01/2020", demijournee=2, etudid=etudid + # ) - _ = sco_abs_views.doJustifAbsence( - "17/01/2020", - "18/01/2020", - demijournee=2, - etudid=etudid, - ) + # _ = sco_abs_views.doJustifAbsence( + # "17/01/2020", + # "18/01/2020", + # demijournee=2, + # etudid=etudid, + # ) - nbabs, nbabsjust = sco_abs.get_abs_count(etudid, sem) - assert nbabs == 6, f"incorrect nbabs ({nbabs})" - assert nbabsjust == 2, f"incorrect nbabsjust ({nbabsjust})" + # nbabs, nbabsjust = sco_abs.get_abs_count(etudid, sem) + # assert nbabs == 6, f"incorrect nbabs ({nbabs})" + # assert nbabsjust == 2, f"incorrect nbabsjust ({nbabsjust})" # --- Permission saisie notes et décisions de jury, avec ou sans démission ou défaillance # on n'a pas encore saisi de décisions