2023-09-20 22:38:01 +02:00
# -*- 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
# module codé par Matthias Hartmann, 2023
#
##############################################################################
2023-04-17 15:44:55 +02:00
import datetime
2023-11-15 14:11:47 +01:00
import re
2023-04-17 15:44:55 +02:00
2023-09-06 11:21:53 +02:00
from flask import g , request , render_template , flash
from flask import abort , url_for , redirect
2023-08-29 19:30:56 +02:00
from flask_login import current_user
2023-04-17 15:44:55 +02:00
2023-07-26 13:27:57 +02:00
from app import db
2023-04-17 15:44:55 +02:00
from app . comp import res_sem
from app . comp . res_compat import NotesTableCompat
from app . decorators import (
scodoc ,
permission_required ,
)
2023-07-25 19:59:47 +02:00
from app . models import (
FormSemestre ,
Identite ,
ScoDocSiteConfig ,
Assiduite ,
2023-09-08 11:59:10 +02:00
Justificatif ,
2023-07-25 19:59:47 +02:00
Departement ,
2023-09-06 11:21:53 +02:00
Evaluation ,
2023-07-25 19:59:47 +02:00
)
2023-04-17 15:44:55 +02:00
from app . views import assiduites_bp as bp
from app . views import ScoData
# ---------------
from app . scodoc . sco_permissions import Permission
from app . scodoc import html_sco_header
2023-08-29 19:30:56 +02:00
from app . scodoc import safehtml
2023-04-17 15:44:55 +02:00
from app . scodoc import sco_moduleimpl
from app . scodoc import sco_preferences
from app . scodoc import sco_groups_view
from app . scodoc import sco_etud
from app . scodoc import sco_find_etud
2023-05-30 17:16:07 +02:00
from app . scodoc import sco_assiduites as scass
2023-08-29 19:30:56 +02:00
from app . scodoc import sco_utils as scu
from app . scodoc . sco_exceptions import ScoValueError
2023-04-17 15:44:55 +02:00
2023-07-10 17:38:50 +02:00
from app . tables . visu_assiduites import TableAssi , etuds_sorted_from_ids
2023-04-17 15:44:55 +02:00
CSSSTYLES = html_sco_header . BOOTSTRAP_MULTISELECT_CSS
# --- UTILS ---
class HTMLElement :
""" Représentation d ' un HTMLElement version Python """
def __init__ ( self , tag : str , * attr , * * kattr ) - > None :
self . tag : str = tag
2023-09-14 12:08:20 +02:00
self . children : list [ " HTMLElement " ] = [ ]
2023-04-17 15:44:55 +02:00
self . self_close : bool = kattr . get ( " self_close " , False )
self . text_content : str = kattr . get ( " text_content " , " " )
self . key_attributes : dict [ str , any ] = kattr
self . attributes : list [ str ] = list ( attr )
2023-09-14 12:08:20 +02:00
def add ( self , * child : " HTMLElement " ) - > None :
2023-04-17 15:44:55 +02:00
""" add child element to self """
for kid in child :
self . children . append ( kid )
2023-09-14 12:08:20 +02:00
def remove ( self , child : " HTMLElement " ) - > None :
2023-04-17 15:44:55 +02:00
""" Remove child element from self """
if child in self . children :
self . children . remove ( child )
def __str__ ( self ) - > str :
attr : list [ str ] = self . attributes
for att , val in self . key_attributes . items ( ) :
if att in ( " self_close " , " text_content " ) :
continue
if att != " cls " :
attr . append ( f ' { att } = " { val } " ' )
else :
attr . append ( f ' class= " { val } " ' )
if not self . self_close :
head : str = f " < { self . tag } { ' ' . join ( attr ) } > { self . text_content } "
body : str = " \n " . join ( map ( str , self . children ) )
foot : str = f " </ { self . tag } > "
return head + body + foot
return f " < { self . tag } { ' ' . join ( attr ) } /> "
def __add__ ( self , other : str ) :
return str ( self ) + other
def __radd__ ( self , other : str ) :
return other + str ( self )
class HTMLStringElement ( HTMLElement ) :
""" Utilisation d ' une chaine de caracètres pour représenter un element """
def __init__ ( self , text : str ) - > None :
self . text : str = text
HTMLElement . __init__ ( self , " textnode " )
def __str__ ( self ) - > str :
return self . text
class HTMLBuilder :
2023-09-12 19:57:39 +02:00
def __init__ ( self , * content : HTMLElement | str ) - > None :
self . content : list [ HTMLElement | str ] = list ( content )
2023-04-17 15:44:55 +02:00
2023-09-12 19:57:39 +02:00
def add ( self , * element : HTMLElement | str ) :
2023-04-17 15:44:55 +02:00
self . content . extend ( element )
2023-09-12 19:57:39 +02:00
def remove ( self , element : HTMLElement | str ) :
2023-04-17 15:44:55 +02:00
if element in self . content :
self . content . remove ( element )
def __str__ ( self ) - > str :
return " \n " . join ( map ( str , self . content ) )
def build ( self ) - > str :
return self . __str__ ( )
# --------------------------------------------------------------------
#
2023-09-21 08:46:21 +02:00
# Assiduité (/ScoDoc/<dept>/Scolarite/Assiduites/...)
2023-04-17 15:44:55 +02:00
#
# --------------------------------------------------------------------
@bp.route ( " / " )
2023-09-13 15:19:21 +02:00
@bp.route ( " /BilanDept " )
2023-04-17 15:44:55 +02:00
@scodoc
2023-09-29 21:17:31 +02:00
@permission_required ( Permission . AbsChange )
2023-09-13 15:19:21 +02:00
def bilan_dept ( ) :
2023-04-17 15:44:55 +02:00
""" Gestionnaire assiduités, page principale """
2023-10-27 16:05:40 +02:00
# Préparation de la page
2023-04-17 15:44:55 +02:00
H = [
html_sco_header . sco_header (
2023-09-19 23:04:46 +02:00
page_title = " Saisie de l ' assiduité " ,
2023-06-28 17:15:24 +02:00
javascripts = [
" js/assiduites.js " ,
2023-11-10 14:24:29 +01:00
" js/date_utils.js " ,
2023-06-28 17:15:24 +02:00
] ,
cssstyles = [
" css/assiduites.css " ,
] ,
2023-04-17 15:44:55 +02:00
) ,
2023-09-19 23:04:46 +02:00
""" <h2>Traitement de l ' assiduité</h2>
2023-04-17 15:44:55 +02:00
< p class = " help " >
2023-11-17 00:22:46 +01:00
Pour saisir l ' assiduité ou consulter les états, il est recommandé de passer par
2023-06-28 17:15:24 +02:00
le semestre concerné ( saisie par jour ou saisie différée ) .
2023-04-17 15:44:55 +02:00
< / p >
""" ,
]
H . append (
2023-11-17 00:22:46 +01:00
""" <p class= " help " >Pour signaler, annuler ou justifier l ' assiduité d ' un seul étudiant,
2023-09-19 23:04:46 +02:00
choisissez d ' abord la personne concernée :</p> " " "
2023-04-17 15:44:55 +02:00
)
2023-10-27 16:05:40 +02:00
# Ajout de la barre de recherche d'étudiant (redirection vers bilan etud)
2023-09-11 11:11:00 +02:00
H . append ( sco_find_etud . form_search_etud ( dest_url = " assiduites.bilan_etud " ) )
2023-10-27 16:05:40 +02:00
# Gestion des billets d'absences
2023-09-11 11:11:00 +02:00
if current_user . has_permission (
2023-09-29 21:17:31 +02:00
Permission . AbsChange
2023-09-11 11:11:00 +02:00
) and sco_preferences . get_preference ( " handle_billets_abs " ) :
H . append (
f """
< h2 style = " margin-top: 30px; " > Billets d ' absence</h2>
< ul > < li > < a href = " { url_for( " absences . list_billets " , scodoc_dept=g.scodoc_dept)
} " >Traitement des billets d ' absence en attente</a>
< / li > < / ul >
"""
)
2023-10-27 16:05:40 +02:00
# Récupération des années d'étude du département
# (afin de sélectionner une année)
2023-09-12 09:41:50 +02:00
dept : Departement = Departement . query . filter_by ( id = g . scodoc_dept_id ) . first ( )
2023-09-12 09:37:03 +02:00
annees : list [ int ] = sorted (
[ f . date_debut . year for f in dept . formsemestres ] ,
reverse = True ,
)
2023-10-27 16:05:40 +02:00
annee = scu . annee_scolaire ( ) # Année courante, sera utilisée par défaut
# Génération d'une liste "json" d'années
2023-09-12 09:37:03 +02:00
annees_str : str = " [ "
for ann in annees :
annees_str + = f " { ann } , "
annees_str + = " ] "
2023-06-28 17:15:24 +02:00
2023-10-27 16:05:40 +02:00
# Récupération d'un formsemestre
# (pour n'afficher que les assiduites/justificatifs liés au formsemestre)
2023-09-13 15:19:21 +02:00
formsemestre_id = request . args . get ( " formsemestre_id " , " " )
if formsemestre_id :
try :
formsemestre : FormSemestre = FormSemestre . get_formsemestre ( formsemestre_id )
annee = formsemestre . annee_scolaire ( )
except AttributeError :
formsemestre_id = " "
2023-10-27 16:05:40 +02:00
# Peuplement du template jinja
2023-06-28 17:15:24 +02:00
H . append (
render_template (
" assiduites/pages/bilan_dept.j2 " ,
dept_id = g . scodoc_dept_id ,
2023-09-13 15:19:21 +02:00
annee = annee ,
2023-09-12 09:37:03 +02:00
annees = annees_str ,
2023-09-13 15:19:21 +02:00
formsemestre_id = formsemestre_id ,
group_id = request . args . get ( " group_id " , " " ) ,
2023-06-28 17:15:24 +02:00
) ,
)
2023-04-17 15:44:55 +02:00
H . append ( html_sco_header . sco_footer ( ) )
return " \n " . join ( H )
@bp.route ( " /SignaleAssiduiteEtud " )
@scodoc
2023-09-29 21:17:31 +02:00
@permission_required ( Permission . AbsChange )
2023-04-17 15:44:55 +02:00
def signal_assiduites_etud ( ) :
"""
signal_assiduites_etud Saisie de l ' assiduité d ' un étudiant
Args :
etudid ( int ) : l ' identifiant de l ' étudiant
Returns :
str : l ' html généré
"""
2023-10-27 16:05:40 +02:00
# Récupération de l'étudiant concerné
2023-04-17 15:44:55 +02:00
etudid = request . args . get ( " etudid " , - 1 )
etud : Identite = Identite . query . get_or_404 ( etudid )
if etud . dept_id != g . scodoc_dept_id :
abort ( 404 , " étudiant inexistant dans ce département " )
2023-10-27 16:05:40 +02:00
# Récupération de la date (par défaut la date du jour)
2023-09-06 11:21:53 +02:00
date = request . args . get ( " date " , datetime . date . today ( ) . isoformat ( ) )
2023-11-13 08:35:19 +01:00
heures : list [ str ] = [
request . args . get ( " heure_deb " , " " ) ,
request . args . get ( " heure_fin " , " " ) ,
]
2023-09-06 11:21:53 +02:00
2023-10-27 16:05:40 +02:00
# gestion évaluations (Appel à la page depuis les évaluations)
2023-09-06 11:21:53 +02:00
saisie_eval : bool = request . args . get ( " saisie_eval " ) is not None
date_deb : str = request . args . get ( " date_deb " )
date_fin : str = request . args . get ( " date_fin " )
moduleimpl_id : int = request . args . get ( " moduleimpl_id " , " " )
evaluation_id : int = request . args . get ( " evaluation_id " )
redirect_url : str = (
" # "
if not saisie_eval
else url_for (
" notes.evaluation_check_absences_html " ,
evaluation_id = evaluation_id ,
scodoc_dept = g . scodoc_dept ,
)
)
2023-10-27 16:05:40 +02:00
# Préparation de la page (Header)
2023-04-17 15:44:55 +02:00
header : str = html_sco_header . sco_header (
2023-09-21 08:46:21 +02:00
page_title = " Saisie assiduité " ,
2023-04-17 15:44:55 +02:00
init_qtip = True ,
javascripts = [
" js/assiduites.js " ,
2023-11-10 14:24:29 +01:00
" js/date_utils.js " ,
2023-09-04 14:27:48 +02:00
" js/etud_info.js " ,
2023-04-17 15:44:55 +02:00
] ,
2023-11-22 15:31:35 +01:00
cssstyles = CSSSTYLES
+ [
2023-04-17 15:44:55 +02:00
" css/assiduites.css " ,
] ,
)
2023-04-25 22:59:06 +02:00
# Gestion des horaires (journée, matin, soir)
2023-09-20 22:38:01 +02:00
morning = ScoDocSiteConfig . assi_get_rounded_time ( " assi_morning_time " , " 08:00:00 " )
lunch = ScoDocSiteConfig . assi_get_rounded_time ( " assi_lunch_time " , " 13:00:00 " )
afternoon = ScoDocSiteConfig . assi_get_rounded_time (
" assi_afternoon_time " , " 18:00:00 "
)
2023-04-25 22:59:06 +02:00
2023-10-27 16:05:40 +02:00
# Gestion du selecteur de moduleimpl (pour le tableau différé)
2023-10-26 15:52:53 +02:00
select = f """
2023-06-12 17:54:30 +02:00
< select class = " dynaSelect " >
2023-10-26 15:52:53 +02:00
{ render_template ( " assiduites/widgets/simplemoduleimpl_select.j2 " ) }
2023-06-12 17:54:30 +02:00
< / select >
"""
2023-10-27 16:05:40 +02:00
# Génération de la page
2023-04-17 15:44:55 +02:00
return HTMLBuilder (
header ,
2023-05-30 10:17:49 +02:00
_mini_timeline ( ) ,
2023-04-17 15:44:55 +02:00
render_template (
2023-11-22 15:31:35 +01:00
" assiduites/pages/ajout_assiduites.j2 " ,
2023-04-17 15:44:55 +02:00
sco = ScoData ( etud ) ,
2023-11-22 15:31:35 +01:00
assi_limit_annee = sco_preferences . get_preference (
" assi_limit_annee " ,
dept_id = g . scodoc_dept_id ,
2023-06-12 17:54:30 +02:00
) ,
2023-11-22 15:31:35 +01:00
assi_morning = ScoDocSiteConfig . get ( " assi_morning_time " , " 08:00 " ) ,
assi_evening = ScoDocSiteConfig . get ( " assi_afternoon_time " , " 18:00 " ) ,
2023-09-06 11:21:53 +02:00
saisie_eval = saisie_eval ,
date_deb = date_deb ,
date_fin = date_fin ,
redirect_url = redirect_url ,
moduleimpl_id = moduleimpl_id ,
2023-04-17 15:44:55 +02:00
) ,
2023-11-22 15:31:35 +01:00
# render_template(
# "assiduites/pages/signal_assiduites_etud.j2",
# sco=ScoData(etud),
# date=_dateiso_to_datefr(date),
# morning=morning,
# lunch=lunch,
# timeline=_timeline(heures=",".join([f"'{s}'" for s in heures])),
# afternoon=afternoon,
# nonworkdays=_non_work_days(),
# forcer_module=sco_preferences.get_preference(
# "forcer_module", dept_id=g.scodoc_dept_id
# ),
# diff=_differee(
# etudiants=[sco_etud.get_etud_info(etudid=etud.etudid, filled=True)[0]],
# moduleimpl_select=select,
# ),
# saisie_eval=saisie_eval,
# date_deb=date_deb,
# date_fin=date_fin,
# redirect_url=redirect_url,
# moduleimpl_id=moduleimpl_id,
# ),
2023-04-17 15:44:55 +02:00
) . build ( )
2023-04-20 18:04:21 +02:00
@bp.route ( " /ListeAssiduitesEtud " )
@scodoc
2023-06-30 17:24:16 +02:00
@permission_required ( Permission . ScoView )
2023-04-20 18:04:21 +02:00
def liste_assiduites_etud ( ) :
"""
liste_assiduites_etud Affichage de toutes les assiduites et justificatifs d ' un etudiant
Args :
etudid ( int ) : l ' identifiant de l ' étudiant
Returns :
str : l ' html généré
"""
2023-10-27 16:05:40 +02:00
# Récupération de l'étudiant concerné
2023-04-20 18:04:21 +02:00
etudid = request . args . get ( " etudid " , - 1 )
etud : Identite = Identite . query . get_or_404 ( etudid )
if etud . dept_id != g . scodoc_dept_id :
abort ( 404 , " étudiant inexistant dans ce département " )
2023-10-27 16:05:40 +02:00
# Gestion d'une assiduité unique (redirigé depuis le calendrier)
2023-09-11 15:55:18 +02:00
assiduite_id : int = request . args . get ( " assiduite_id " , - 1 )
2023-10-27 16:05:40 +02:00
# Préparation de la page
2023-04-20 18:04:21 +02:00
header : str = html_sco_header . sco_header (
2023-09-19 23:04:46 +02:00
page_title = f " Assiduité de { etud . nomprenom } " ,
2023-04-20 18:04:21 +02:00
init_qtip = True ,
javascripts = [
" js/assiduites.js " ,
2023-11-10 14:24:29 +01:00
" js/date_utils.js " ,
2023-04-20 18:04:21 +02:00
] ,
cssstyles = CSSSTYLES
+ [
" css/assiduites.css " ,
] ,
)
2023-10-27 16:05:40 +02:00
# Peuplement du template jinja
2023-04-20 18:04:21 +02:00
return HTMLBuilder (
header ,
render_template (
2023-06-20 08:33:49 +02:00
" assiduites/pages/liste_assiduites.j2 " ,
2023-04-20 18:04:21 +02:00
sco = ScoData ( etud ) ,
date = datetime . date . today ( ) . isoformat ( ) ,
2023-09-11 15:55:18 +02:00
assi_id = assiduite_id ,
2023-09-12 09:37:03 +02:00
assi_limit_annee = sco_preferences . get_preference (
" assi_limit_annee " ,
dept_id = g . scodoc_dept_id ,
) ,
2023-04-20 18:04:21 +02:00
) ,
) . build ( )
2023-06-28 17:15:24 +02:00
@bp.route ( " /BilanEtud " )
@scodoc
2023-06-30 17:24:16 +02:00
@permission_required ( Permission . ScoView )
2023-06-28 17:15:24 +02:00
def bilan_etud ( ) :
"""
bilan_etud Affichage de toutes les assiduites et justificatifs d ' un etudiant
Args :
etudid ( int ) : l ' identifiant de l ' étudiant
Returns :
str : l ' html généré
"""
2023-10-27 16:05:40 +02:00
# Récupération de l'étudiant
2023-06-28 17:15:24 +02:00
etudid = request . args . get ( " etudid " , - 1 )
etud : Identite = Identite . query . get_or_404 ( etudid )
if etud . dept_id != g . scodoc_dept_id :
abort ( 404 , " étudiant inexistant dans ce département " )
2023-10-27 16:05:40 +02:00
# Préparation de la page (header)
2023-06-28 17:15:24 +02:00
header : str = html_sco_header . sco_header (
2023-09-19 23:04:46 +02:00
page_title = f " Bilan de l ' assiduité de { etud . nomprenom } " ,
2023-06-28 17:15:24 +02:00
init_qtip = True ,
javascripts = [
" js/assiduites.js " ,
2023-11-10 14:24:29 +01:00
" js/date_utils.js " ,
2023-06-28 17:15:24 +02:00
] ,
cssstyles = CSSSTYLES
+ [
" css/assiduites.css " ,
] ,
)
2023-10-27 16:05:40 +02:00
# Gestion des dates du bilan (par défaut l'année scolaire)
2023-11-15 14:11:47 +01:00
date_debut : str = f " 01/09/ { scu . annee_scolaire ( ) } "
date_fin : str = f " 30/06/ { scu . annee_scolaire ( ) + 1 } "
2023-06-28 17:15:24 +02:00
2023-10-27 16:05:40 +02:00
# Récupération de la métrique d'assiduité
2023-08-22 15:43:10 +02:00
assi_metric = scu . translate_assiduites_metric (
sco_preferences . get_preference ( " assi_metrique " , dept_id = g . scodoc_dept_id ) ,
2023-08-14 01:08:04 +02:00
)
2023-06-28 17:15:24 +02:00
2023-10-27 16:05:40 +02:00
# Génération de la page
2023-06-28 17:15:24 +02:00
return HTMLBuilder (
header ,
render_template (
" assiduites/pages/bilan_etud.j2 " ,
sco = ScoData ( etud ) ,
date_debut = date_debut ,
date_fin = date_fin ,
assi_metric = assi_metric ,
assi_seuil = _get_seuil ( ) ,
2023-09-12 09:37:03 +02:00
assi_limit_annee = sco_preferences . get_preference (
" assi_limit_annee " ,
dept_id = g . scodoc_dept_id ,
) ,
2023-06-28 17:15:24 +02:00
) ,
) . build ( )
2023-06-20 15:50:56 +02:00
@bp.route ( " /AjoutJustificatifEtud " )
@scodoc
2023-09-29 21:17:31 +02:00
@permission_required ( Permission . AbsChange )
2023-06-20 15:50:56 +02:00
def ajout_justificatif_etud ( ) :
"""
ajout_justificatif_etud : Affichage et création / modification des justificatifs de l ' étudiant
Args :
etudid ( int ) : l ' identifiant de l ' étudiant
Returns :
str : l ' html généré
"""
2023-10-27 16:05:40 +02:00
# Récupération de l'étudiant concerné
2023-06-20 15:50:56 +02:00
etudid = request . args . get ( " etudid " , - 1 )
etud : Identite = Identite . query . get_or_404 ( etudid )
if etud . dept_id != g . scodoc_dept_id :
abort ( 404 , " étudiant inexistant dans ce département " )
2023-10-27 16:05:40 +02:00
# Préparation de la page (header)
2023-06-20 15:50:56 +02:00
header : str = html_sco_header . sco_header (
page_title = " Justificatifs " ,
init_qtip = True ,
javascripts = [
" js/assiduites.js " ,
2023-11-10 14:24:29 +01:00
" js/date_utils.js " ,
2023-06-20 15:50:56 +02:00
] ,
cssstyles = CSSSTYLES
+ [
" css/assiduites.css " ,
] ,
)
2023-10-27 16:05:40 +02:00
# Peuplement du template jinja
2023-06-20 15:50:56 +02:00
return HTMLBuilder (
header ,
render_template (
" assiduites/pages/ajout_justificatif.j2 " ,
sco = ScoData ( etud ) ,
2023-09-12 09:37:03 +02:00
assi_limit_annee = sco_preferences . get_preference (
" assi_limit_annee " ,
dept_id = g . scodoc_dept_id ,
) ,
2023-09-12 09:41:50 +02:00
assi_morning = ScoDocSiteConfig . get ( " assi_morning_time " , " 08:00 " ) ,
2023-09-27 08:27:13 +02:00
assi_evening = ScoDocSiteConfig . get ( " assi_afternoon_time " , " 18:00 " ) ,
2023-06-20 15:50:56 +02:00
) ,
) . build ( )
2023-06-22 16:25:13 +02:00
@bp.route ( " /CalendrierAssiduitesEtud " )
@scodoc
@permission_required ( Permission . ScoView )
def calendrier_etud ( ) :
"""
calendrier_etud : Affichage d ' un calendrier des assiduités de l ' étudiant
Args :
etudid ( int ) : l ' identifiant de l ' étudiant
Returns :
str : l ' html généré
"""
2023-10-27 16:05:40 +02:00
# Récupération de l'étudiant
2023-06-22 16:25:13 +02:00
etudid = request . args . get ( " etudid " , - 1 )
etud : Identite = Identite . query . get_or_404 ( etudid )
if etud . dept_id != g . scodoc_dept_id :
abort ( 404 , " étudiant inexistant dans ce département " )
2023-10-27 16:05:40 +02:00
# Préparation de la page
2023-06-22 16:25:13 +02:00
header : str = html_sco_header . sco_header (
2023-09-21 08:46:21 +02:00
page_title = " Calendrier de l ' assiduité " ,
2023-06-22 16:25:13 +02:00
init_qtip = True ,
javascripts = [
" js/assiduites.js " ,
2023-11-10 14:24:29 +01:00
" js/date_utils.js " ,
2023-06-22 16:25:13 +02:00
] ,
cssstyles = CSSSTYLES
+ [
" css/assiduites.css " ,
] ,
)
2023-10-27 16:05:40 +02:00
# Récupération des années d'étude de l'étudiant
2023-11-15 17:34:13 +01:00
annees : list [ int ] = [ ]
for ins in etud . formsemestre_inscriptions :
annees . extend (
( ins . formsemestre . date_debut . year , ins . formsemestre . date_fin . year )
)
annees = sorted ( annees , reverse = True )
2023-08-22 16:06:56 +02:00
2023-10-27 16:05:40 +02:00
# Transformation en une liste "json"
# (sera utilisé pour générer le selecteur d'année)
2023-08-22 16:06:56 +02:00
annees_str : str = " [ "
for ann in annees :
annees_str + = f " { ann } , "
annees_str + = " ] "
2023-10-27 16:05:40 +02:00
# Peuplement du template jinja
2023-06-22 16:25:13 +02:00
return HTMLBuilder (
header ,
render_template (
" assiduites/pages/calendrier.j2 " ,
sco = ScoData ( etud ) ,
annee = scu . annee_scolaire ( ) ,
nonworkdays = _non_work_days ( ) ,
minitimeline = _mini_timeline ( ) ,
2023-08-22 16:06:56 +02:00
annees = annees_str ,
2023-06-22 16:25:13 +02:00
) ,
) . build ( )
2023-04-17 15:44:55 +02:00
@bp.route ( " /SignalAssiduiteGr " )
@scodoc
2023-09-29 21:17:31 +02:00
@permission_required ( Permission . AbsChange )
2023-04-17 15:44:55 +02:00
def signal_assiduites_group ( ) :
"""
signal_assiduites_group Saisie des assiduités des groupes pour le jour donnée
Returns :
str : l ' html généré
"""
2023-10-27 16:05:40 +02:00
# Récupération des paramètres de l'url
2023-04-17 15:44:55 +02:00
formsemestre_id : int = request . args . get ( " formsemestre_id " , - 1 )
moduleimpl_id : int = request . args . get ( " moduleimpl_id " )
date : str = request . args . get ( " jour " , datetime . date . today ( ) . isoformat ( ) )
2023-11-13 08:35:19 +01:00
heures : list [ str ] = [
request . args . get ( " heure_deb " , " " ) ,
request . args . get ( " heure_fin " , " " ) ,
]
2023-04-17 15:44:55 +02:00
group_ids : list [ int ] = request . args . get ( " group_ids " , None )
2023-07-26 16:43:49 +02:00
if group_ids is None :
group_ids = [ ]
else :
group_ids = group_ids . split ( " , " )
map ( str , group_ids )
# Vérification du moduleimpl_id
try :
moduleimpl_id = int ( moduleimpl_id )
except ( TypeError , ValueError ) :
moduleimpl_id = None
2023-10-27 16:05:40 +02:00
2023-07-26 16:43:49 +02:00
# Vérification du formsemestre_id
try :
formsemestre_id = int ( formsemestre_id )
except ( TypeError , ValueError ) :
formsemestre_id = None
2023-10-27 16:05:40 +02:00
# Gestion des groupes
2023-07-26 16:43:49 +02:00
groups_infos = sco_groups_view . DisplayedGroupsInfos (
group_ids , moduleimpl_id = moduleimpl_id , formsemestre_id = formsemestre_id
)
if not groups_infos . members :
return (
2023-09-21 08:46:21 +02:00
html_sco_header . sco_header ( page_title = " Saisie journalière de l ' assiduité " )
2023-07-26 16:43:49 +02:00
+ " <h3>Aucun étudiant ! </h3> "
+ html_sco_header . sco_footer ( )
)
# --- Filtrage par formsemestre ---
formsemestre_id = groups_infos . formsemestre_id
formsemestre : FormSemestre = FormSemestre . query . get_or_404 ( formsemestre_id )
if formsemestre . dept_id != g . scodoc_dept_id :
abort ( 404 , " groupes inexistants dans ce département " )
2023-10-27 16:05:40 +02:00
# Vérification du forçage du module
2023-10-26 15:52:53 +02:00
require_module = sco_preferences . get_preference ( " forcer_module " , formsemestre_id )
2023-10-27 16:05:40 +02:00
# Récupération des étudiants des groupes
2023-07-26 16:43:49 +02:00
etuds = [
sco_etud . get_etud_info ( etudid = m [ " etudid " ] , filled = True ) [ 0 ]
for m in groups_infos . members
]
# --- Vérification de la date ---
real_date = scu . is_iso_formated ( date , True ) . date ( )
2023-09-13 08:59:54 +02:00
if real_date < formsemestre . date_debut or real_date > formsemestre . date_fin :
2023-10-27 16:05:40 +02:00
# Si le jour est hors semestre, indiquer une erreur
# Formatage des dates pour le message d'erreur
2023-09-13 08:59:54 +02:00
real_str = real_date . strftime ( " %d / % m/ % Y " )
form_deb = formsemestre . date_debut . strftime ( " %d / % m/ % Y " )
form_fin = formsemestre . date_fin . strftime ( " %d / % m/ % Y " )
raise ScoValueError (
2023-09-19 23:04:46 +02:00
f " Impossible de saisir l ' assiduité pour le { real_str } "
2023-09-13 08:59:54 +02:00
+ f " : Jour en dehors du semestre ( { form_deb } → { form_fin } ) "
)
2023-07-26 16:43:49 +02:00
# --- Restriction en fonction du moduleimpl_id ---
if moduleimpl_id :
mod_inscrits = {
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
2023-10-27 16:05:40 +02:00
# Récupération du nom des/du groupe(s)
2023-07-26 16:43:49 +02:00
if groups_infos . tous_les_etuds_du_sem :
gr_tit = " en "
else :
if len ( groups_infos . group_ids ) > 1 :
grp = " des groupes "
else :
grp = " du groupe "
gr_tit = (
grp + ' <span class= " fontred " > ' + groups_infos . groups_titles + " </span> "
)
2023-10-27 16:05:40 +02:00
# --- Génération de l'HTML ---
2023-07-26 16:43:49 +02:00
header : str = html_sco_header . sco_header (
page_title = " Saisie journalière des assiduités " ,
init_qtip = True ,
javascripts = html_sco_header . BOOTSTRAP_MULTISELECT_JS
+ [
# Voir fonctionnement JS
" js/etud_info.js " ,
" js/groups_view.js " ,
" js/assiduites.js " ,
2023-11-10 14:24:29 +01:00
" js/date_utils.js " ,
2023-07-26 16:43:49 +02:00
] ,
cssstyles = CSSSTYLES
+ [
" css/assiduites.css " ,
] ,
)
2023-10-27 16:05:40 +02:00
# Récupération du semestre en dictionnaire
sem = formsemestre . to_dict ( )
# Peuplement du template jinja
2023-07-26 16:43:49 +02:00
return HTMLBuilder (
header ,
_mini_timeline ( ) ,
render_template (
" assiduites/pages/signal_assiduites_group.j2 " ,
gr_tit = gr_tit ,
sem = sem [ " titre_num " ] ,
2023-11-15 14:11:47 +01:00
date = _dateiso_to_datefr ( date ) ,
2023-07-26 16:43:49 +02:00
formsemestre_id = formsemestre_id ,
grp = sco_groups_view . menu_groups_choice ( groups_infos ) ,
moduleimpl_select = _module_selector ( formsemestre , moduleimpl_id ) ,
2023-11-13 08:35:19 +01:00
timeline = _timeline ( heures = " , " . join ( [ f " ' { s } ' " for s in heures ] ) ) ,
2023-07-26 16:43:49 +02:00
nonworkdays = _non_work_days ( ) ,
formsemestre_date_debut = str ( formsemestre . date_debut ) ,
formsemestre_date_fin = str ( formsemestre . date_fin ) ,
forcer_module = sco_preferences . get_preference (
" forcer_module " ,
formsemestre_id = formsemestre_id ,
dept_id = g . scodoc_dept_id ,
) ,
defdem = _get_etuds_dem_def ( formsemestre ) ,
2023-07-27 14:58:57 +02:00
readonly = " false " ,
2023-07-26 16:43:49 +02:00
) ,
html_sco_header . sco_footer ( ) ,
) . build ( )
@bp.route ( " /VisuAssiduiteGr " )
@scodoc
@permission_required ( Permission . ScoView )
def visu_assiduites_group ( ) :
"""
2023-09-18 22:47:25 +02:00
Visualisation des assiduités des groupes pour le jour donné
dans le formsemestre_id et le moduleimpl_id
2023-07-26 16:43:49 +02:00
Returns :
str : l ' html généré
"""
2023-10-27 16:05:40 +02:00
# Récupération des paramètres de la requête
2023-07-26 16:43:49 +02:00
formsemestre_id : int = request . args . get ( " formsemestre_id " , - 1 )
moduleimpl_id : int = request . args . get ( " moduleimpl_id " )
date : str = request . args . get ( " jour " , datetime . date . today ( ) . isoformat ( ) )
group_ids : list [ int ] = request . args . get ( " group_ids " , None )
2023-04-17 15:44:55 +02:00
if group_ids is None :
group_ids = [ ]
else :
group_ids = group_ids . split ( " , " )
map ( str , group_ids )
# Vérification du moduleimpl_id
2023-09-18 22:47:25 +02:00
if moduleimpl_id is not None :
try :
moduleimpl_id = int ( moduleimpl_id )
except ( TypeError , ValueError ) as exc :
raise ScoValueError ( " identifiant de moduleimpl invalide " ) from exc
2023-04-17 15:44:55 +02:00
# Vérification du formsemestre_id
2023-09-18 22:47:25 +02:00
if formsemestre_id is not None :
try :
formsemestre_id = int ( formsemestre_id )
except ( TypeError , ValueError ) as exc :
raise ScoValueError ( " identifiant de formsemestre invalide " ) from exc
2023-04-17 15:44:55 +02:00
2023-10-27 16:05:40 +02:00
# Récupérations des/du groupe(s)
2023-04-17 15:44:55 +02:00
groups_infos = sco_groups_view . DisplayedGroupsInfos (
group_ids , moduleimpl_id = moduleimpl_id , formsemestre_id = formsemestre_id
)
if not groups_infos . members :
return (
2023-09-21 08:46:21 +02:00
html_sco_header . sco_header ( page_title = " Saisie journalière de l ' assiduité " )
2023-04-17 15:44:55 +02:00
+ " <h3>Aucun étudiant ! </h3> "
+ html_sco_header . sco_footer ( )
)
# --- Filtrage par formsemestre ---
formsemestre_id = groups_infos . formsemestre_id
2023-06-01 17:32:50 +02:00
2023-04-17 15:44:55 +02:00
formsemestre : FormSemestre = FormSemestre . query . get_or_404 ( formsemestre_id )
if formsemestre . dept_id != g . scodoc_dept_id :
abort ( 404 , " groupes inexistants dans ce département " )
2023-10-27 16:05:40 +02:00
# Vérfication du forçage du module
require_module = sco_preferences . get_preference ( " forcer_module " , formsemestre_id )
2023-04-17 15:44:55 +02:00
2023-10-27 16:05:40 +02:00
# Récupération des étudiants du/des groupe(s)
2023-04-17 15:44:55 +02:00
etuds = [
sco_etud . get_etud_info ( etudid = m [ " etudid " ] , filled = True ) [ 0 ]
for m in groups_infos . members
]
2023-06-12 17:54:30 +02:00
# --- Vérification de la date ---
real_date = scu . is_iso_formated ( date , True ) . date ( )
if real_date < formsemestre . date_debut :
date = formsemestre . date_debut . isoformat ( )
elif real_date > formsemestre . date_fin :
date = formsemestre . date_fin . isoformat ( )
2023-04-17 15:44:55 +02:00
# --- Restriction en fonction du moduleimpl_id ---
if moduleimpl_id :
mod_inscrits = {
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
# --- Génération de l'HTML ---
if groups_infos . tous_les_etuds_du_sem :
gr_tit = " en "
else :
if len ( groups_infos . group_ids ) > 1 :
grp = " des groupes "
else :
grp = " du groupe "
gr_tit = (
grp + ' <span class= " fontred " > ' + groups_infos . groups_titles + " </span> "
)
header : str = html_sco_header . sco_header (
2023-09-19 23:04:46 +02:00
page_title = " Saisie journalière de l ' assiduité " ,
2023-04-17 15:44:55 +02:00
init_qtip = True ,
javascripts = html_sco_header . BOOTSTRAP_MULTISELECT_JS
+ [
# Voir fonctionnement JS
" js/etud_info.js " ,
" js/groups_view.js " ,
" js/assiduites.js " ,
2023-11-10 14:24:29 +01:00
" js/date_utils.js " ,
2023-04-17 15:44:55 +02:00
] ,
cssstyles = CSSSTYLES
+ [
" css/assiduites.css " ,
] ,
)
2023-10-27 16:05:40 +02:00
# Récupération du semestre en dictionnaire
sem = formsemestre . to_dict ( )
2023-04-17 15:44:55 +02:00
return HTMLBuilder (
header ,
2023-05-30 10:17:49 +02:00
_mini_timeline ( ) ,
2023-04-17 15:44:55 +02:00
render_template (
2023-06-20 08:33:49 +02:00
" assiduites/pages/signal_assiduites_group.j2 " ,
2023-04-17 15:44:55 +02:00
gr_tit = gr_tit ,
sem = sem [ " titre_num " ] ,
2023-11-15 14:11:47 +01:00
date = _dateiso_to_datefr ( date ) ,
2023-04-17 15:44:55 +02:00
formsemestre_id = formsemestre_id ,
grp = sco_groups_view . menu_groups_choice ( groups_infos ) ,
moduleimpl_select = _module_selector ( formsemestre , moduleimpl_id ) ,
timeline = _timeline ( ) ,
2023-05-30 11:47:59 +02:00
nonworkdays = _non_work_days ( ) ,
2023-04-17 15:44:55 +02:00
formsemestre_date_debut = str ( formsemestre . date_debut ) ,
formsemestre_date_fin = str ( formsemestre . date_fin ) ,
2023-04-25 22:59:06 +02:00
forcer_module = sco_preferences . get_preference (
" forcer_module " ,
formsemestre_id = formsemestre_id ,
dept_id = g . scodoc_dept_id ,
) ,
2023-07-25 19:59:47 +02:00
defdem = _get_etuds_dem_def ( formsemestre ) ,
2023-07-27 14:58:57 +02:00
readonly = " true " ,
2023-04-17 15:44:55 +02:00
) ,
html_sco_header . sco_footer ( ) ,
) . build ( )
2023-08-30 08:53:36 +02:00
@bp.route ( " /etat_abs_date " )
2023-05-30 17:16:07 +02:00
@scodoc
2023-06-01 17:32:50 +02:00
@permission_required ( Permission . ScoView )
2023-08-30 08:53:36 +02:00
def etat_abs_date ( ) :
2023-08-29 19:30:56 +02:00
""" date_debut, date_fin en ISO """
2023-10-27 16:05:40 +02:00
# Récupération des paramètre de la requête
2023-08-29 19:30:56 +02:00
date_debut_str = request . args . get ( " date_debut " )
date_fin_str = request . args . get ( " date_fin " )
title = request . args . get ( " desc " )
2023-05-30 17:16:07 +02:00
group_ids : list [ int ] = request . args . get ( " group_ids " , None )
2023-10-27 16:05:40 +02:00
# Vérification des dates
2023-08-29 19:30:56 +02:00
try :
date_debut = datetime . datetime . fromisoformat ( date_debut_str )
except ValueError as exc :
raise ScoValueError ( " date_debut invalide " ) from exc
try :
date_fin = datetime . datetime . fromisoformat ( date_fin_str )
except ValueError as exc :
raise ScoValueError ( " date_fin invalide " ) from exc
2023-10-27 16:05:40 +02:00
# Vérification des groupes
2023-05-30 17:16:07 +02:00
if group_ids is None :
group_ids = [ ]
else :
group_ids = group_ids . split ( " , " )
map ( str , group_ids )
groups_infos = sco_groups_view . DisplayedGroupsInfos ( group_ids )
2023-10-27 16:05:40 +02:00
# Récupération des étudiants des groupes
2023-05-30 17:16:07 +02:00
etuds = [
sco_etud . get_etud_info ( etudid = m [ " etudid " ] , filled = True ) [ 0 ]
for m in groups_infos . members
]
2023-10-27 16:05:40 +02:00
# Récupération des assiduites des étudiants
2023-05-30 17:16:07 +02:00
assiduites : Assiduite = Assiduite . query . filter (
Assiduite . etudid . in_ ( [ e [ " etudid " ] for e in etuds ] )
)
2023-10-27 16:05:40 +02:00
# Filtrage des assiduités en fonction des dates données
2023-05-30 17:16:07 +02:00
assiduites = scass . filter_by_date (
assiduites , Assiduite , date_debut , date_fin , False
)
2023-10-27 16:05:40 +02:00
# Génération d'objet étudiant simplifié (nom+lien cal, etat_assiduite)
2023-08-25 17:59:57 +02:00
etudiants : list [ dict ] = [ ]
2023-05-30 17:16:07 +02:00
for etud in etuds :
2023-10-27 16:05:40 +02:00
# On récupère l'état de la première assiduité sur la période
2023-05-30 17:16:07 +02:00
assi = assiduites . filter_by ( etudid = etud [ " etudid " ] ) . first ( )
etat = " "
2023-08-25 17:59:57 +02:00
if assi is not None and assi . etat != 0 :
2023-05-30 17:16:07 +02:00
etat = scu . EtatAssiduite . inverse ( ) . get ( assi . etat ) . name
2023-10-27 16:05:40 +02:00
# On génère l'objet simplifié
2023-05-30 17:16:07 +02:00
etudiant = {
2023-08-25 17:59:57 +02:00
" nom " : f """ <a href= " { url_for (
2023-11-17 00:22:46 +01:00
" assiduites.calendrier_etud " ,
scodoc_dept = g . scodoc_dept ,
2023-08-25 17:59:57 +02:00
etudid = etud [ " etudid " ] )
} " ><font color= " #A00000">{etud["nomprenom"]}</font></a>""",
2023-05-30 17:16:07 +02:00
" etat " : etat ,
}
etudiants . append ( etudiant )
2023-10-27 16:05:40 +02:00
# On tri les étudiants
2023-05-30 17:16:07 +02:00
etudiants = list ( sorted ( etudiants , key = lambda x : x [ " nom " ] ) )
2023-10-27 16:05:40 +02:00
# Génération de l'HTML
2023-05-30 17:16:07 +02:00
header : str = html_sco_header . sco_header (
2023-08-29 19:30:56 +02:00
page_title = safehtml . html_to_safe_html ( title ) ,
2023-05-30 17:16:07 +02:00
init_qtip = True ,
)
return HTMLBuilder (
header ,
render_template (
2023-06-20 08:33:49 +02:00
" assiduites/pages/etat_absence_date.j2 " ,
etudiants = etudiants ,
2023-08-25 17:59:57 +02:00
group_title = groups_infos . groups_titles ,
date_debut = date_debut ,
date_fin = date_fin ,
2023-05-30 17:16:07 +02:00
) ,
html_sco_header . sco_footer ( ) ,
) . build ( )
2023-07-10 17:38:50 +02:00
@bp.route ( " /VisualisationAssiduitesGroupe " )
@scodoc
@permission_required ( Permission . ScoView )
def visu_assi_group ( ) :
2023-09-18 22:47:25 +02:00
""" Visualisation de l ' assiduité d ' un groupe entre deux dates """
2023-10-27 16:05:40 +02:00
# Récupération des paramètres de la requête
2023-07-10 17:38:50 +02:00
dates = {
" debut " : request . args . get ( " date_debut " ) ,
" fin " : request . args . get ( " date_fin " ) ,
}
2023-09-21 10:20:19 +02:00
fmt = request . args . get ( " fmt " , " html " )
2023-07-10 17:38:50 +02:00
group_ids : list [ int ] = request . args . get ( " group_ids " , None )
if group_ids is None :
group_ids = [ ]
else :
group_ids = group_ids . split ( " , " )
map ( str , group_ids )
2023-10-27 16:05:40 +02:00
# Récupération des groupes, du semestre et des étudiants
2023-07-10 17:38:50 +02:00
groups_infos = sco_groups_view . DisplayedGroupsInfos ( group_ids )
2023-07-26 13:27:57 +02:00
formsemestre = db . session . get ( FormSemestre , groups_infos . formsemestre_id )
2023-07-10 17:38:50 +02:00
etuds = etuds_sorted_from_ids ( [ m [ " etudid " ] for m in groups_infos . members ] )
2023-10-27 16:05:40 +02:00
# Génération du tableau des assiduités
2023-07-26 13:27:57 +02:00
table : TableAssi = TableAssi (
etuds = etuds , dates = list ( dates . values ( ) ) , formsemestre = formsemestre
)
2023-07-10 17:38:50 +02:00
2023-10-27 16:05:40 +02:00
# Export en XLS
2023-07-20 15:53:59 +02:00
if fmt . startswith ( " xls " ) :
return scu . send_file (
table . excel ( ) ,
filename = f " assiduite- { groups_infos . groups_filename } " ,
mime = scu . XLSX_MIMETYPE ,
suffix = scu . XLSX_SUFFIX ,
)
2023-10-27 16:05:40 +02:00
# récupération du/des noms du/des groupes
2023-07-10 17:38:50 +02:00
if groups_infos . tous_les_etuds_du_sem :
2023-07-25 14:03:09 +02:00
gr_tit = " "
grp = " "
2023-07-10 17:38:50 +02:00
else :
if len ( groups_infos . group_ids ) > 1 :
grp = " des groupes "
else :
grp = " du groupe "
gr_tit = (
grp + ' <span class= " fontred " > ' + groups_infos . groups_titles + " </span> "
)
2023-10-27 16:05:40 +02:00
# Génération de la page
2023-07-20 15:53:59 +02:00
return render_template (
" assiduites/pages/visu_assi.j2 " ,
2023-08-22 15:43:10 +02:00
assi_metric = scu . translate_assiduites_metric (
scu . translate_assiduites_metric (
sco_preferences . get_preference (
" assi_metrique " , dept_id = g . scodoc_dept_id
) ,
) ,
inverse = False ,
short = False ,
2023-08-13 22:40:16 +02:00
) ,
2023-11-15 14:11:47 +01:00
date_debut = _dateiso_to_datefr ( dates [ " debut " ] ) ,
2023-11-17 11:36:15 +01:00
date_fin = _dateiso_to_datefr ( dates [ " fin " ] ) ,
2023-08-13 22:40:16 +02:00
gr_tit = gr_tit ,
2023-07-20 15:53:59 +02:00
group_ids = request . args . get ( " group_ids " , None ) ,
sco = ScoData ( formsemestre = groups_infos . get_formsemestre ( ) ) ,
2023-08-13 22:40:16 +02:00
tableau = table . html ( ) ,
2023-07-20 15:53:59 +02:00
title = f " Assiduité { grp } { groups_infos . groups_titles } " ,
)
2023-07-10 17:38:50 +02:00
2023-06-01 17:32:50 +02:00
@bp.route ( " /SignalAssiduiteDifferee " )
@scodoc
2023-09-29 21:17:31 +02:00
@permission_required ( Permission . AbsChange )
2023-06-01 17:32:50 +02:00
def signal_assiduites_diff ( ) :
2023-10-27 16:05:40 +02:00
# Récupération des paramètres de la requête
2023-06-01 17:32:50 +02:00
group_ids : list [ int ] = request . args . get ( " group_ids " , None )
formsemestre_id : int = request . args . get ( " formsemestre_id " , - 1 )
2023-06-12 17:54:30 +02:00
date : str = request . args . get ( " jour " , datetime . date . today ( ) . isoformat ( ) )
2023-09-04 14:27:48 +02:00
date_deb : str = request . args . get ( " date_deb " )
date_fin : str = request . args . get ( " date_fin " )
semaine : str = request . args . get ( " semaine " )
2023-10-27 16:05:40 +02:00
# Dans le cas où on donne une semaine plutot qu'un jour
2023-09-04 14:27:48 +02:00
if semaine is not None :
2023-10-27 16:05:40 +02:00
# On génère la semaine iso à partir de l'anne scolaire.
2023-09-04 14:27:48 +02:00
semaine = (
f " { scu . annee_scolaire ( ) } -W { semaine } " if " W " not in semaine else semaine
)
2023-10-27 16:05:40 +02:00
# On met à jour les dates avec le date de debut et fin de semaine
2023-09-04 14:27:48 +02:00
date_deb : datetime . date = datetime . datetime . strptime (
semaine + " -1 " , " % Y-W % W- % w "
)
date_fin : datetime . date = date_deb + datetime . timedelta ( days = 6 )
2023-06-01 17:32:50 +02:00
etudiants : list [ dict ] = [ ]
titre = None
# Vérification du formsemestre_id
try :
formsemestre_id = int ( formsemestre_id )
except ( TypeError , ValueError ) :
formsemestre_id = None
formsemestre : FormSemestre = FormSemestre . query . get_or_404 ( formsemestre_id )
2023-06-12 17:54:30 +02:00
# --- Vérification de la date ---
real_date = scu . is_iso_formated ( date , True ) . date ( )
if real_date < formsemestre . date_debut :
date = formsemestre . date_debut . isoformat ( )
elif real_date > formsemestre . date_fin :
date = formsemestre . date_fin . isoformat ( )
2023-10-27 16:05:40 +02:00
# Vérification des groupes
2023-06-01 17:32:50 +02:00
if group_ids is None :
group_ids = [ ]
else :
group_ids = group_ids . split ( " , " )
map ( str , group_ids )
groups_infos = sco_groups_view . DisplayedGroupsInfos ( group_ids )
2023-06-05 16:18:06 +02:00
if not groups_infos . members :
return (
2023-09-19 23:04:46 +02:00
html_sco_header . sco_header ( page_title = " Assiduité: saisie différée " )
2023-06-05 16:18:06 +02:00
+ " <h3>Aucun étudiant ! </h3> "
+ html_sco_header . sco_footer ( )
)
2023-10-27 16:05:40 +02:00
# Récupération des étudiants
2023-06-01 17:32:50 +02:00
etudiants . extend (
[
sco_etud . get_etud_info ( etudid = m [ " etudid " ] , filled = True ) [ 0 ]
for m in groups_infos . members
]
)
etudiants = list ( sorted ( etudiants , key = lambda x : x [ " nom " ] ) )
2023-10-27 16:05:40 +02:00
# Génération de l'HTML
2023-06-01 17:32:50 +02:00
header : str = html_sco_header . sco_header (
2023-09-19 23:04:46 +02:00
page_title = " Assiduité: saisie différée " ,
2023-06-01 17:32:50 +02:00
init_qtip = True ,
2023-06-12 17:54:30 +02:00
cssstyles = [
" css/assiduites.css " ,
] ,
2023-06-01 17:32:50 +02:00
javascripts = html_sco_header . BOOTSTRAP_MULTISELECT_JS
+ [
" js/assiduites.js " ,
2023-11-10 14:24:29 +01:00
" js/date_utils.js " ,
2023-09-04 14:27:48 +02:00
" js/etud_info.js " ,
2023-06-01 17:32:50 +02:00
] ,
)
sem = formsemestre . to_dict ( )
if groups_infos . tous_les_etuds_du_sem :
gr_tit = " en "
else :
if len ( groups_infos . group_ids ) > 1 :
grp = " des groupes "
else :
grp = " du groupe "
gr_tit = (
grp + ' <span class= " fontred " > ' + groups_infos . groups_titles + " </span> "
)
return HTMLBuilder (
header ,
render_template (
2023-06-20 08:33:49 +02:00
" assiduites/pages/signal_assiduites_diff.j2 " ,
2023-06-12 17:54:30 +02:00
diff = _differee (
etudiants = etudiants ,
2023-09-06 15:09:43 +02:00
moduleimpl_select = _module_selector (
formsemestre , request . args . get ( " moduleimpl_id " , None )
) ,
2023-06-12 17:54:30 +02:00
date = date ,
periode = {
" deb " : formsemestre . date_debut . isoformat ( ) ,
" fin " : formsemestre . date_fin . isoformat ( ) ,
} ,
2023-06-05 16:18:06 +02:00
) ,
2023-06-01 17:32:50 +02:00
gr = gr_tit ,
sem = sem [ " titre_num " ] ,
2023-07-25 19:59:47 +02:00
defdem = _get_etuds_dem_def ( formsemestre ) ,
2023-09-04 14:27:48 +02:00
timeMorning = ScoDocSiteConfig . get ( " assi_morning_time " , " 08:00:00 " ) ,
timeNoon = ScoDocSiteConfig . get ( " assi_lunch_time " , " 13:00:00 " ) ,
2023-09-27 08:27:13 +02:00
timeEvening = ScoDocSiteConfig . get ( " assi_afternoon_time " , " 18:00:00 " ) ,
2023-09-04 14:27:48 +02:00
defaultDates = _get_days_between_dates ( date_deb , date_fin ) ,
nonworkdays = _non_work_days ( ) ,
2023-06-01 17:32:50 +02:00
) ,
html_sco_header . sco_footer ( ) ,
) . build ( )
2023-09-06 11:21:53 +02:00
@bp.route ( " /SignalEvaluationAbs/<int:evaluation_id>/<int:etudid> " )
@scodoc
@permission_required ( Permission . ScoView )
def signal_evaluation_abs ( etudid : int = None , evaluation_id : int = None ) :
"""
Signale l ' absence d ' un étudiant à une évaluation
Si la durée de l ' évaluation est inférieur à 1 jour
Alors l ' absence sera sur la période de l ' évaluation
Sinon L ' utilisateur sera redirigé vers la page de saisie des absences de l ' étudiant
"""
2023-10-27 16:05:40 +02:00
# Récupération de l'étudiant concerné
2023-09-06 11:21:53 +02:00
etud : Identite = Identite . query . get_or_404 ( etudid )
if etud . dept_id != g . scodoc_dept_id :
abort ( 404 , " étudiant inexistant dans ce département " )
2023-10-27 16:05:40 +02:00
# Récupération de l'évaluation concernée
2023-09-06 11:21:53 +02:00
evaluation : Evaluation = Evaluation . query . get_or_404 ( evaluation_id )
delta : datetime . timedelta = evaluation . date_fin - evaluation . date_debut
2023-10-27 16:05:40 +02:00
# Si l'évaluation dure plus qu'un jour alors on redirige vers la page de saisie etudiant
2023-09-06 11:21:53 +02:00
if delta > datetime . timedelta ( days = 1 ) :
# rediriger vers page saisie
return redirect (
url_for (
" assiduites.signal_assiduites_etud " ,
etudid = etudid ,
evaluation_id = evaluation . id ,
date_deb = evaluation . date_debut . strftime ( " % Y- % m- %d T % H: % M: % S " ) ,
date_fin = evaluation . date_fin . strftime ( " % Y- % m- %d T % H: % M: % S " ) ,
moduleimpl_id = evaluation . moduleimpl . id ,
saisie_eval = " true " ,
scodoc_dept = g . scodoc_dept ,
)
)
2023-10-27 16:05:40 +02:00
# Sinon on créé l'assiduité
2023-09-06 11:21:53 +02:00
2023-09-07 09:17:25 +02:00
try :
assiduite_unique : Assiduite = Assiduite . create_assiduite (
etud = etud ,
date_debut = scu . localize_datetime ( evaluation . date_debut ) ,
date_fin = scu . localize_datetime ( evaluation . date_fin ) ,
etat = scu . EtatAssiduite . ABSENT ,
moduleimpl = evaluation . moduleimpl ,
)
except ScoValueError as see :
2023-10-27 16:05:40 +02:00
# En cas d'erreur
2023-09-07 09:17:25 +02:00
msg : str = see . args [ 0 ]
if " Duplication " in msg :
msg = " Une autre assiduité concerne déjà cette période. En cliquant sur continuer vous serez redirigé vers la page de saisie des assiduités de l ' étudiant. "
dest : str = url_for (
" assiduites.signal_assiduites_etud " ,
etudid = etudid ,
evaluation_id = evaluation . id ,
date_deb = evaluation . date_debut . strftime ( " % Y- % m- %d T % H: % M: % S " ) ,
date_fin = evaluation . date_fin . strftime ( " % Y- % m- %d T % H: % M: % S " ) ,
moduleimpl_id = evaluation . moduleimpl . id ,
saisie_eval = " true " ,
scodoc_dept = g . scodoc_dept ,
duplication = " oui " ,
)
2023-09-08 11:59:10 +02:00
raise ScoValueError ( msg , dest ) from see
2023-09-06 11:21:53 +02:00
db . session . add ( assiduite_unique )
db . session . commit ( )
2023-10-27 16:05:40 +02:00
# on flash pour indiquer que l'absence a bien été créée puis on revient sur la page de l'évaluation
2023-09-06 11:21:53 +02:00
flash ( " L ' absence a bien été créée " )
# rediriger vers la page d'évaluation
return redirect (
url_for (
" notes.evaluation_check_absences_html " ,
evaluation_id = evaluation . id ,
scodoc_dept = g . scodoc_dept ,
)
)
2023-09-08 11:59:10 +02:00
def generate_bul_list ( etud : Identite , semestre : FormSemestre ) - > str :
""" Génère la liste des assiduités d ' un étudiant pour le bulletin mail """
2023-10-27 16:05:40 +02:00
# On récupère la métrique d'assiduité
2023-09-08 11:59:10 +02:00
metrique : str = scu . translate_assiduites_metric (
sco_preferences . get_preference ( " assi_metrique " , formsemestre_id = semestre . id ) ,
)
2023-10-27 16:05:40 +02:00
# On récupère le nombre maximum de ligne d'assiduité
2023-09-08 11:59:10 +02:00
max_nb : int = int (
sco_preferences . get_preference (
" bul_mail_list_abs_nb " , formsemestre_id = semestre . id
)
)
2023-10-27 16:05:40 +02:00
# On récupère les assiduités et les justificatifs de l'étudiant
2023-09-08 11:59:10 +02:00
assiduites = scass . filter_by_formsemestre (
etud . assiduites , Assiduite , semestre
) . order_by ( Assiduite . entry_date . desc ( ) )
justificatifs = scass . filter_by_formsemestre (
etud . justificatifs , Justificatif , semestre
) . order_by ( Justificatif . entry_date . desc ( ) )
2023-10-27 16:05:40 +02:00
# On calcule les statistiques
2023-09-08 11:59:10 +02:00
stats : dict = scass . get_assiduites_stats (
assiduites , metric = metrique , filtered = { " split " : True }
)
2023-11-13 08:35:19 +01:00
# On sépare :
2023-10-27 16:05:40 +02:00
# - abs_j = absences justifiées
# - abs_nj = absences non justifiées
# - retards = les retards
# - justifs = les justificatifs
2023-11-13 08:35:19 +01:00
2023-09-08 11:59:10 +02:00
abs_j : list [ str ] = [
{ " date " : _get_date_str ( assi . date_debut , assi . date_fin ) }
for assi in assiduites
if assi . etat == scu . EtatAssiduite . ABSENT and assi . est_just is True
]
abs_nj : list [ str ] = [
{ " date " : _get_date_str ( assi . date_debut , assi . date_fin ) }
for assi in assiduites
if assi . etat == scu . EtatAssiduite . ABSENT and assi . est_just is False
]
retards : list [ str ] = [
{ " date " : _get_date_str ( assi . date_debut , assi . date_fin ) }
for assi in assiduites
if assi . etat == scu . EtatAssiduite . RETARD
]
justifs : list [ dict [ str , str ] ] = [
{
" date " : _get_date_str ( justi . date_debut , justi . date_fin ) ,
" raison " : " " if justi . raison is None else justi . raison ,
" etat " : {
scu . EtatJustificatif . VALIDE : " justificatif valide " ,
scu . EtatJustificatif . NON_VALIDE : " justificatif invalide " ,
scu . EtatJustificatif . ATTENTE : " justificatif en attente de validation " ,
scu . EtatJustificatif . MODIFIE : " justificatif ayant été modifié " ,
} . get ( justi . etat ) ,
}
for justi in justificatifs
]
return render_template (
" assiduites/widgets/liste_assiduites_mail.j2 " ,
abs_j = abs_j [ : max_nb ] ,
abs_nj = abs_nj [ : max_nb ] ,
retards = retards [ : max_nb ] ,
justifs = justifs [ : max_nb ] ,
stats = stats ,
metrique = scu . translate_assiduites_metric ( metrique , short = True , inverse = False ) ,
metric = metrique ,
)
2023-11-10 16:00:13 +01:00
@bp.route ( " /test " , methods = [ " GET " , " POST " ] )
@scodoc
@permission_required ( Permission . ScoView )
def test ( ) :
""" XXX fonction de test a retirer """
if request . method == " POST " :
print ( " test date_utils : " , request . form )
return render_template ( " assiduites/pages/test.j2 " )
2023-09-06 11:21:53 +02:00
# --- Fonctions internes ---
2023-11-15 14:11:47 +01:00
def _dateiso_to_datefr ( date_iso : str ) - > str :
"""
_dateiso_to_datefr Transforme une date iso en date format français
Args :
date_iso ( str ) : date au format iso ( YYYY - MM - DD )
Raises :
ValueError : Si l ' argument `date_iso` n ' est pas au bon format
Returns :
str : date au format français ( DD / MM / YYYY )
"""
regex_date_iso : str = r " ^ \ d {4} -([0] \ d|1[0-2])-([0-2] \ d|3[01])$ "
# Vérification de la date_iso
if not re . match ( regex_date_iso , date_iso ) :
raise ValueError (
f " La dateiso passée en paramètre [ { date_iso } ] n ' est pas valide. "
)
return f " { date_iso [ 8 : 10 ] } / { date_iso [ 5 : 7 ] } / { date_iso [ 0 : 4 ] } "
2023-09-08 11:59:10 +02:00
def _get_date_str ( deb : datetime . datetime , fin : datetime . datetime ) - > str :
2023-10-27 16:05:40 +02:00
"""
_get_date_str transforme une période en chaîne lisible
Args :
deb ( datetime . datetime ) : date de début
fin ( datetime . datetime ) : date de fin
Returns :
str :
" le dd/mm/yyyy de hh:MM à hh:MM " si les deux date sont sur le même jour
" du dd/mm/yyyy hh:MM audd/mm/yyyy hh:MM " sinon
"""
2023-09-08 11:59:10 +02:00
if deb . date ( ) == fin . date ( ) :
temps = deb . strftime ( " %d / % m/ % Y % H: % M " ) . split ( " " ) + [ fin . strftime ( " % H: % M " ) ]
return f " le { temps [ 0 ] } de { temps [ 1 ] } à { temps [ 2 ] } "
return f ' du { deb . strftime ( " %d / % m/ % Y % H: % M " ) } au { fin . strftime ( " %d / % m/ % Y % H: % M " ) } '
2023-10-27 16:05:40 +02:00
def _get_days_between_dates ( deb : str , fin : str ) - > str :
"""
_get_days_between_dates récupère tous les jours entre deux dates
Args :
deb ( str ) : date de début
fin ( str ) : date de fin
Returns :
str : une chaine json représentant une liste des jours
[ ' date_iso ' , ' date_iso2 ' , . . . ]
"""
2023-09-04 14:27:48 +02:00
if deb is None or fin is None :
return " null "
try :
if isinstance ( deb , str ) and isinstance ( fin , str ) :
date_deb : datetime . date = datetime . date . fromisoformat ( deb )
date_fin : datetime . date = datetime . date . fromisoformat ( fin )
else :
date_deb , date_fin = deb . date ( ) , fin . date ( )
except ValueError :
return " null "
dates : list [ str ] = [ ]
while date_deb < = date_fin :
dates . append ( f ' " { date_deb . isoformat ( ) } " ' )
date_deb = date_deb + datetime . timedelta ( days = 1 )
return f " [ { ' , ' . join ( dates ) } ] "
2023-06-12 17:54:30 +02:00
def _differee (
2023-10-27 16:05:40 +02:00
etudiants : list [ dict ] ,
moduleimpl_select : str ,
date : str = None ,
periode : dict [ str , str ] = None ,
formsemestre_id : int = None ,
) - > str :
"""
_differee Génère un tableau de saisie différé
Args :
etudiants ( list [ dict ] ) : la liste des étudiants ( représentés par des dictionnaires )
moduleimpl_select ( str ) : l ' html représentant le selecteur de module
date ( str , optional ) : la première date à afficher . Defaults to None .
periode ( dict [ str , str ] , optional ) : La période par défaut de la première colonne . Defaults to None .
formsemestre_id ( int , optional ) : l ' id du semestre pour le selecteur de module. Defaults to None.
Returns :
str : le widget ( html / css / js )
"""
2023-06-12 17:54:30 +02:00
if date is None :
date = datetime . date . today ( ) . isoformat ( )
forcer_module = sco_preferences . get_preference (
" forcer_module " ,
formsemestre_id = formsemestre_id ,
dept_id = g . scodoc_dept_id ,
)
2023-10-07 10:14:50 +02:00
assi_etat_defaut = sco_preferences . get_preference (
2023-06-12 17:54:30 +02:00
" assi_etat_defaut " ,
formsemestre_id = formsemestre_id ,
dept_id = g . scodoc_dept_id ,
)
return render_template (
2023-06-20 08:33:49 +02:00
" assiduites/widgets/differee.j2 " ,
2023-06-12 17:54:30 +02:00
etudiants = etudiants ,
2023-10-07 10:14:50 +02:00
assi_etat_defaut = assi_etat_defaut ,
2023-06-12 17:54:30 +02:00
forcer_module = forcer_module ,
moduleimpl_select = moduleimpl_select ,
date = date ,
periode = periode ,
)
2023-10-27 16:05:40 +02:00
def _module_selector ( formsemestre : FormSemestre , moduleimpl_id : int = None ) - > str :
2023-04-17 15:44:55 +02:00
"""
_module_selector Génère un HTMLSelectElement à partir des moduleimpl du formsemestre
Args :
formsemestre ( FormSemestre ) : Le formsemestre d ' où les moduleimpls seront pris.
Returns :
str : La représentation str d ' un HTMLSelectElement
"""
2023-10-27 16:05:40 +02:00
# récupération des ues du semestre
2023-04-17 15:44:55 +02:00
ntc : NotesTableCompat = res_sem . load_formsemestre_results ( formsemestre )
2023-10-27 16:05:40 +02:00
ues = ntc . get_ues_stat_dict ( )
2023-04-17 15:44:55 +02:00
modimpls_list : list [ dict ] = [ ]
for ue in ues :
2023-10-27 16:05:40 +02:00
# Ajout des moduleimpl de chaque ue dans la liste des moduleimpls
2023-04-17 15:44:55 +02:00
modimpls_list + = ntc . get_modimpls_dict ( ue_id = ue [ " ue_id " ] )
2023-10-27 16:05:40 +02:00
# prévoie la sélection par défaut d'un moduleimpl s'il a été passé en paramètre
2023-09-01 15:24:44 +02:00
selected = " " if moduleimpl_id is not None else " selected "
2023-04-17 15:44:55 +02:00
2023-10-27 16:05:40 +02:00
# Vérification que le moduleimpl_id passé en paramètre est bien un entier
try :
moduleimpl_id = int ( moduleimpl_id )
except ( ValueError , TypeError ) :
moduleimpl_id = None
2023-04-17 15:44:55 +02:00
2023-10-27 16:05:40 +02:00
modules : list [ dict [ str , str | int ] ] = [ ]
# Récupération de l'id et d'un nom lisible pour chaque moduleimpl
2023-04-17 15:44:55 +02:00
for modimpl in modimpls_list :
modname : str = (
( modimpl [ " module " ] [ " code " ] or " " )
+ " "
+ ( modimpl [ " module " ] [ " abbrev " ] or modimpl [ " module " ] [ " titre " ] or " " )
)
modules . append ( { " moduleimpl_id " : modimpl [ " moduleimpl_id " ] , " name " : modname } )
return render_template (
2023-09-01 15:24:44 +02:00
" assiduites/widgets/moduleimpl_selector.j2 " ,
selected = selected ,
modules = modules ,
moduleimpl_id = moduleimpl_id ,
2023-04-17 15:44:55 +02:00
)
2023-10-27 16:05:40 +02:00
def _dynamic_module_selector ( ) - > str :
"""
_dynamic_module_selector retourne l ' html/css/javascript du selecteur de module dynamique
Returns :
str : l ' html/css/javascript du selecteur de module dynamique
"""
2023-10-26 15:52:53 +02:00
return render_template (
" assiduites/widgets/moduleimpl_dynamic_selector.j2 " ,
)
2023-06-12 17:54:30 +02:00
2023-11-13 08:35:19 +01:00
def _timeline ( formsemestre_id : int = None , heures = None ) - > str :
2023-10-27 16:05:40 +02:00
"""
_timeline retourne l ' html de la timeline
Args :
formsemestre_id ( int , optional ) : un formsemestre . Defaults to None .
Le formsemestre sert à obtenir la période par défaut de la timeline
sinon ce sera de 2 heure dès le début de la timeline
Returns :
str : l ' html en chaîne de caractères
"""
2023-04-25 22:59:06 +02:00
return render_template (
2023-06-20 08:33:49 +02:00
" assiduites/widgets/timeline.j2 " ,
2023-09-20 22:38:01 +02:00
t_start = ScoDocSiteConfig . assi_get_rounded_time ( " assi_morning_time " , " 08:00:00 " ) ,
t_end = ScoDocSiteConfig . assi_get_rounded_time ( " assi_afternoon_time " , " 18:00:00 " ) ,
2023-06-02 17:19:55 +02:00
tick_time = ScoDocSiteConfig . get ( " assi_tick_time " , 15 ) ,
2023-05-17 20:46:10 +02:00
periode_defaut = sco_preferences . get_preference (
" periode_defaut " , formsemestre_id
) ,
2023-11-13 08:35:19 +01:00
heures = heures ,
2023-04-25 22:59:06 +02:00
)
2023-05-30 10:17:49 +02:00
2023-10-27 16:05:40 +02:00
def _mini_timeline ( ) - > str :
"""
_mini_timeline Retourne l ' html lié au mini timeline d ' assiduités
Returns :
str : l ' html en chaîne de caractères
"""
2023-05-30 10:17:49 +02:00
return render_template (
2023-06-20 08:33:49 +02:00
" assiduites/widgets/minitimeline.j2 " ,
2023-09-20 22:38:01 +02:00
t_start = ScoDocSiteConfig . assi_get_rounded_time ( " assi_morning_time " , " 08:00:00 " ) ,
t_end = ScoDocSiteConfig . assi_get_rounded_time ( " assi_afternoon_time " , " 18:00:00 " ) ,
2023-05-30 10:17:49 +02:00
)
2023-06-12 17:54:30 +02:00
2023-09-20 22:38:01 +02:00
def _non_work_days ( ) - > str :
""" Abbréviation des jours non travaillés: " ' sam ' , ' dim ' " .
donnés par les préférences du département
"""
non_travail = sco_preferences . get_preference ( " non_travail " )
2023-06-12 17:54:30 +02:00
non_travail = non_travail . replace ( " " , " " ) . split ( " , " )
return " , " . join ( [ f " ' { i . lower ( ) } ' " for i in non_travail ] )
2023-09-20 22:38:01 +02:00
def _get_seuil ( ) - > int :
""" Seuil d ' alerte des absences (en unité de la métrique),
tel que fixé dans les préférences du département . """
2023-06-28 17:15:24 +02:00
return sco_preferences . get_preference ( " assi_seuil " , dept_id = g . scodoc_dept_id )
2023-07-25 19:59:47 +02:00
2023-09-20 22:38:01 +02:00
def _get_etuds_dem_def ( formsemestre ) - > str :
""" Une chaine json donnant les étudiants démissionnaires ou défaillants
du formsemestre , sous la forme
' { " 516 " : " D " , ... } '
"""
return (
" { "
+ " , " . join (
[
f ' " { ins . etudid } " : " { ins . etat } " '
for ins in formsemestre . inscriptions
if ins . etat != scu . INSCRIT
]
)
+ " } "
)