2023-09-20 22:38:01 +02:00
|
|
|
# -*- coding: utf-8 -*-
|
|
|
|
##############################################################################
|
|
|
|
#
|
|
|
|
# Gestion scolarite IUT
|
|
|
|
#
|
2023-12-31 23:04:06 +01:00
|
|
|
# Copyright (c) 1999 - 2024 Emmanuel Viennet. All rights reserved.
|
2023-09-20 22:38:01 +02:00
|
|
|
#
|
|
|
|
# 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-12-07 12:38:47 +01:00
|
|
|
from typing import Any
|
2023-04-17 15:44:55 +02:00
|
|
|
|
2023-09-06 11:21:53 +02:00
|
|
|
from flask import g, request, render_template, flash
|
2023-12-01 13:46:33 +01:00
|
|
|
from flask import abort, url_for, redirect, Response
|
2023-08-29 19:30:56 +02:00
|
|
|
from flask_login import current_user
|
2023-04-17 15:44:55 +02:00
|
|
|
|
2023-12-12 03:05:31 +01:00
|
|
|
from app import db, log
|
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-12-12 03:05:31 +01:00
|
|
|
from app.forms.assiduite.ajout_assiduite_etud import (
|
|
|
|
AjoutAssiOrJustForm,
|
|
|
|
AjoutAssiduiteEtudForm,
|
|
|
|
AjoutJustificatifEtudForm,
|
2024-01-12 10:52:40 +01:00
|
|
|
ChoixDateForm,
|
2023-12-12 03:05:31 +01:00
|
|
|
)
|
2023-07-25 19:59:47 +02:00
|
|
|
from app.models import (
|
|
|
|
Assiduite,
|
|
|
|
Departement,
|
2023-09-06 11:21:53 +02:00
|
|
|
Evaluation,
|
2023-12-06 02:04:10 +01:00
|
|
|
FormSemestre,
|
2023-12-09 15:53:45 +01:00
|
|
|
GroupDescr,
|
2023-12-06 02:04:10 +01:00
|
|
|
Identite,
|
|
|
|
Justificatif,
|
|
|
|
ModuleImpl,
|
|
|
|
ScoDocSiteConfig,
|
2023-07-25 19:59:47 +02:00
|
|
|
)
|
2023-12-05 21:04:38 +01:00
|
|
|
from app.scodoc.codes_cursus import UE_STANDARD
|
2023-11-24 13:58:03 +01:00
|
|
|
from app.auth.models import User
|
2023-12-12 03:05:31 +01:00
|
|
|
from app.models.assiduites import get_assiduites_justif, compute_assiduites_justified
|
2023-12-09 15:53:45 +01:00
|
|
|
from app.tables.list_etuds import RowEtud, TableEtud
|
2023-11-24 13:58:03 +01:00
|
|
|
import app.tables.liste_assiduites as liste_assi
|
|
|
|
|
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
|
|
|
|
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-11-24 13:58:03 +01:00
|
|
|
from app.scodoc.sco_archives_justificatifs import JustificatifArchiver
|
2023-07-10 17:38:50 +02:00
|
|
|
|
2024-01-11 17:19:56 +01:00
|
|
|
from flask_sqlalchemy.query import Query
|
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", "")
|
2023-12-07 12:38:47 +01:00
|
|
|
self.key_attributes: dict[str, Any] = kattr
|
2023-04-17 15:44:55 +02:00
|
|
|
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("/")
|
2024-01-12 11:42:17 +01:00
|
|
|
@bp.route("/bilan_dept")
|
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)
|
|
|
|
|
|
|
|
|
2023-12-07 12:38:47 +01:00
|
|
|
@bp.route("/ajout_assiduite_etud", methods=["GET", "POST"])
|
2023-04-17 15:44:55 +02:00
|
|
|
@scodoc
|
2023-09-29 21:17:31 +02:00
|
|
|
@permission_required(Permission.AbsChange)
|
2023-12-14 20:50:27 +01:00
|
|
|
def ajout_assiduite_etud() -> str | Response:
|
2023-04-17 15:44:55 +02:00
|
|
|
"""
|
2023-12-05 21:04:38 +01:00
|
|
|
ajout_assiduite_etud Saisie d'une assiduité d'un étudiant
|
2023-04-17 15:44:55 +02:00
|
|
|
|
|
|
|
Args:
|
|
|
|
etudid (int): l'identifiant de l'étudiant
|
2023-12-01 13:46:33 +01:00
|
|
|
date_deb, date_fin: heures début et fin (ISO sans timezone)
|
|
|
|
moduleimpl_id
|
2023-12-05 21:04:38 +01:00
|
|
|
evaluation_id : si présent, mode "évaluation"
|
|
|
|
fmt: si xls, renvoie le tableau des assiduités enregistrées
|
2023-04-17 15:44:55 +02:00
|
|
|
Returns:
|
|
|
|
str: l'html généré
|
|
|
|
"""
|
2023-12-14 20:50:27 +01:00
|
|
|
etudid: int = request.args.get("etudid", -1)
|
2023-12-01 13:46:33 +01:00
|
|
|
etud = Identite.get_etud(etudid)
|
2023-09-06 11:21:53 +02:00
|
|
|
|
2023-12-01 13:46:33 +01:00
|
|
|
# Gestion évaluations (appel à la page depuis les évaluations)
|
2023-12-14 20:50:27 +01:00
|
|
|
evaluation_id: int | None = request.args.get("evaluation_id")
|
2023-12-05 21:04:38 +01:00
|
|
|
saisie_eval = evaluation_id is not None
|
2023-12-14 20:50:27 +01:00
|
|
|
moduleimpl_id: int | None = request.args.get("moduleimpl_id", "")
|
2023-12-05 21:04:38 +01:00
|
|
|
|
2023-09-06 11:21:53 +02:00
|
|
|
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-12-05 21:04:38 +01:00
|
|
|
form = AjoutAssiduiteEtudForm(request.form)
|
|
|
|
# On dresse la liste des modules de l'année scolaire en cours
|
|
|
|
# auxquels est inscrit l'étudiant pour peupler le menu "module"
|
|
|
|
modimpls_by_formsemestre = etud.get_modimpls_by_formsemestre(scu.annee_scolaire())
|
|
|
|
choices = {
|
|
|
|
"": [("", "Non spécifié"), ("autre", "Autre module (pas dans la liste)")]
|
|
|
|
}
|
|
|
|
for formsemestre_id in modimpls_by_formsemestre:
|
|
|
|
formsemestre: FormSemestre = FormSemestre.query.get(formsemestre_id)
|
|
|
|
# indique le nom du semestre dans le menu (optgroup)
|
|
|
|
choices[formsemestre.titre_annee()] = [
|
2023-12-07 16:11:21 +01:00
|
|
|
(m.id, f"{m.module.code} {m.module.abbrev or m.module.titre or ''}")
|
2023-12-05 21:04:38 +01:00
|
|
|
for m in modimpls_by_formsemestre[formsemestre_id]
|
|
|
|
if m.module.ue.type == UE_STANDARD
|
|
|
|
]
|
|
|
|
form.modimpl.choices = choices
|
|
|
|
|
|
|
|
if form.validate_on_submit():
|
|
|
|
if form.cancel.data: # cancel button
|
|
|
|
return redirect(redirect_url)
|
|
|
|
ok = _record_assiduite_etud(etud, form)
|
|
|
|
if ok:
|
|
|
|
flash("enregistré")
|
|
|
|
return redirect(redirect_url)
|
2023-04-17 15:44:55 +02:00
|
|
|
|
2023-12-05 21:04:38 +01:00
|
|
|
# Le tableau des assiduités+justificatifs déjà en base:
|
2023-12-01 13:46:33 +01:00
|
|
|
is_html, tableau = _prepare_tableau(
|
|
|
|
liste_assi.AssiJustifData.from_etudiants(
|
2023-11-24 18:07:30 +01:00
|
|
|
etud,
|
|
|
|
),
|
2023-12-12 03:05:31 +01:00
|
|
|
filename=f"assiduite-{etud.nom or ''}",
|
2023-11-24 13:58:03 +01:00
|
|
|
afficher_etu=False,
|
2023-12-01 13:46:33 +01:00
|
|
|
filtre=liste_assi.AssiFiltre(type_obj=1),
|
|
|
|
options=liste_assi.AssiDisplayOptions(show_module=True),
|
2024-01-11 17:24:01 +01:00
|
|
|
cache_key=f"tableau-etud-{etud.id}",
|
2023-11-24 13:58:03 +01:00
|
|
|
)
|
2023-12-01 13:46:33 +01:00
|
|
|
if not is_html:
|
|
|
|
return tableau
|
2023-12-05 21:04:38 +01:00
|
|
|
|
|
|
|
return render_template(
|
|
|
|
"assiduites/pages/ajout_assiduite_etud.j2",
|
|
|
|
etud=etud,
|
|
|
|
form=form,
|
|
|
|
moduleimpl_id=moduleimpl_id,
|
|
|
|
redirect_url=redirect_url,
|
|
|
|
sco=ScoData(etud),
|
|
|
|
tableau=tableau,
|
|
|
|
scu=scu,
|
|
|
|
)
|
|
|
|
|
|
|
|
|
2023-12-12 03:05:31 +01:00
|
|
|
def _get_dates_from_assi_form(
|
|
|
|
form: AjoutAssiOrJustForm,
|
2023-12-14 20:50:27 +01:00
|
|
|
) -> tuple[
|
|
|
|
bool, datetime.datetime | None, datetime.datetime | None, datetime.datetime | None
|
|
|
|
]:
|
2023-12-12 03:05:31 +01:00
|
|
|
"""Prend les dates et heures du form, les vérifie
|
|
|
|
puis converti en deux datetime, en timezone du serveur.
|
|
|
|
Ramène ok=True si ok.
|
|
|
|
Met des messages d'erreur dans le form.
|
2023-12-05 21:04:38 +01:00
|
|
|
"""
|
2024-01-05 10:06:16 +01:00
|
|
|
debut_jour = ScoDocSiteConfig.get("assi_morning_time", "08:00")
|
|
|
|
fin_jour = ScoDocSiteConfig.get("assi_afternoon_time", "17:00")
|
2023-12-07 12:38:47 +01:00
|
|
|
date_fin = None
|
2023-12-05 21:04:38 +01:00
|
|
|
# On commence par convertir individuellement tous les champs
|
|
|
|
try:
|
|
|
|
date_debut = datetime.datetime.strptime(form.date_debut.data, "%d/%m/%Y")
|
|
|
|
except ValueError:
|
2023-12-12 23:26:41 +01:00
|
|
|
date_debut = None
|
2023-12-14 20:50:27 +01:00
|
|
|
form.set_error("date début invalide", form.date_debut)
|
2023-12-05 21:04:38 +01:00
|
|
|
try:
|
|
|
|
date_fin = (
|
|
|
|
datetime.datetime.strptime(form.date_fin.data, "%d/%m/%Y")
|
|
|
|
if form.date_fin.data
|
|
|
|
else None
|
|
|
|
)
|
|
|
|
except ValueError:
|
2023-12-12 23:26:41 +01:00
|
|
|
date_fin = None
|
2023-12-14 20:50:27 +01:00
|
|
|
form.set_error("date fin invalide", form.date_fin)
|
2023-12-05 21:04:38 +01:00
|
|
|
|
|
|
|
if date_fin:
|
|
|
|
# ignore les heures si plusieurs jours
|
|
|
|
heure_debut = datetime.time.fromisoformat(debut_jour) # 0h
|
|
|
|
heure_fin = datetime.time.fromisoformat(fin_jour) # minuit
|
|
|
|
else:
|
|
|
|
try:
|
|
|
|
heure_debut = datetime.time.fromisoformat(
|
|
|
|
form.heure_debut.data or debut_jour
|
|
|
|
)
|
|
|
|
except ValueError:
|
2023-12-14 20:50:27 +01:00
|
|
|
form.set_error("heure début invalide", form.heure_debut)
|
|
|
|
if bool(form.heure_debut.data) != bool(form.heure_fin.data):
|
|
|
|
form.set_error(
|
|
|
|
"Les deux heures début et fin doivent être spécifiées, ou aucune"
|
|
|
|
)
|
2023-12-05 21:04:38 +01:00
|
|
|
try:
|
|
|
|
heure_fin = datetime.time.fromisoformat(form.heure_fin.data or fin_jour)
|
|
|
|
except ValueError:
|
2023-12-14 20:50:27 +01:00
|
|
|
form.set_error("heure fin invalide", form.heure_fin)
|
|
|
|
|
|
|
|
if not form.ok:
|
|
|
|
return False, None, None, None
|
|
|
|
|
2023-12-12 03:05:31 +01:00
|
|
|
# Vérifie cohérence des dates/heures
|
2023-12-14 20:50:27 +01:00
|
|
|
dt_debut = datetime.datetime.combine(date_debut, heure_debut)
|
|
|
|
dt_fin = datetime.datetime.combine(date_fin or date_debut, heure_fin)
|
|
|
|
if dt_fin <= dt_debut:
|
|
|
|
form.set_error("dates début/fin incohérentes")
|
2023-12-12 03:05:31 +01:00
|
|
|
# La date de dépot (si vide, la date actuelle)
|
2023-12-14 20:50:27 +01:00
|
|
|
try:
|
|
|
|
dt_entry_date = (
|
|
|
|
datetime.datetime.strptime(form.entry_date.data, "%d/%m/%Y")
|
|
|
|
if form.entry_date.data
|
2023-12-22 15:31:30 +01:00
|
|
|
else datetime.datetime.now() # local tz
|
2023-12-14 20:50:27 +01:00
|
|
|
)
|
|
|
|
except ValueError:
|
|
|
|
dt_entry_date = None
|
|
|
|
form.set_error("format de date de dépôt invalide", form.entry_date)
|
|
|
|
|
2023-12-12 03:05:31 +01:00
|
|
|
# Ajoute time zone serveur
|
2023-12-14 20:50:27 +01:00
|
|
|
dt_debut_tz_server = scu.TIME_ZONE.localize(dt_debut)
|
|
|
|
dt_fin_tz_server = scu.TIME_ZONE.localize(dt_fin)
|
2023-12-12 03:05:31 +01:00
|
|
|
dt_entry_date_tz_server = (
|
|
|
|
scu.TIME_ZONE.localize(dt_entry_date) if dt_entry_date else None
|
|
|
|
)
|
2023-12-14 20:50:27 +01:00
|
|
|
return form.ok, dt_debut_tz_server, dt_fin_tz_server, dt_entry_date_tz_server
|
2023-12-12 03:05:31 +01:00
|
|
|
|
|
|
|
|
|
|
|
def _record_assiduite_etud(
|
|
|
|
etud: Identite,
|
|
|
|
form: AjoutAssiduiteEtudForm,
|
|
|
|
) -> bool:
|
|
|
|
"""Enregistre les données du formulaire de saisie assiduité.
|
|
|
|
Returns ok if successfully recorded, else put error info in the form.
|
|
|
|
Format attendu des données du formulaire:
|
|
|
|
form.assi_etat.data : 'absent'
|
|
|
|
form.date_debut.data : '05/12/2023'
|
|
|
|
form.heure_debut.data : '09:06' (heure locale du serveur)
|
|
|
|
"""
|
|
|
|
(
|
|
|
|
ok,
|
|
|
|
dt_debut_tz_server,
|
|
|
|
dt_fin_tz_server,
|
|
|
|
dt_entry_date_tz_server,
|
|
|
|
) = _get_dates_from_assi_form(form)
|
2023-12-05 21:04:38 +01:00
|
|
|
# Le module (avec "autre")
|
|
|
|
mod_data = form.modimpl.data
|
|
|
|
if mod_data:
|
|
|
|
if mod_data == "autre":
|
|
|
|
moduleimpl_id = "autre"
|
|
|
|
else:
|
|
|
|
try:
|
|
|
|
moduleimpl_id = int(mod_data)
|
|
|
|
except ValueError:
|
|
|
|
form.modimpl.error("choix de module invalide")
|
|
|
|
ok = False
|
|
|
|
else:
|
|
|
|
moduleimpl_id = None
|
2023-12-12 03:05:31 +01:00
|
|
|
|
2023-12-05 21:04:38 +01:00
|
|
|
if not ok:
|
|
|
|
return False
|
2023-12-12 03:05:31 +01:00
|
|
|
|
2023-12-06 02:04:10 +01:00
|
|
|
external_data = None
|
|
|
|
moduleimpl: ModuleImpl | None = None
|
|
|
|
match moduleimpl_id:
|
|
|
|
case "autre":
|
|
|
|
external_data = {"module": "Autre"}
|
|
|
|
case None:
|
|
|
|
moduleimpl = None
|
|
|
|
case _:
|
|
|
|
moduleimpl = ModuleImpl.query.get(moduleimpl_id)
|
|
|
|
try:
|
2024-01-16 16:15:48 +01:00
|
|
|
assi_etat: scu.EtatAssiduite = scu.EtatAssiduite.get(form.assi_etat.data)
|
|
|
|
|
2023-12-06 02:04:10 +01:00
|
|
|
ass = Assiduite.create_assiduite(
|
|
|
|
etud,
|
|
|
|
dt_debut_tz_server,
|
|
|
|
dt_fin_tz_server,
|
2024-01-16 16:15:48 +01:00
|
|
|
assi_etat,
|
2023-12-22 15:59:24 +01:00
|
|
|
description=form.description.data,
|
2023-12-06 02:04:10 +01:00
|
|
|
entry_date=dt_entry_date_tz_server,
|
|
|
|
external_data=external_data,
|
|
|
|
moduleimpl=moduleimpl,
|
|
|
|
notify_mail=True,
|
|
|
|
user_id=current_user.id,
|
|
|
|
)
|
|
|
|
db.session.add(ass)
|
|
|
|
db.session.commit()
|
2024-01-08 19:01:54 +01:00
|
|
|
|
2024-01-16 16:15:48 +01:00
|
|
|
if assi_etat != scu.EtatAssiduite.PRESENT and form.est_just.data:
|
|
|
|
# si la case "justifiée est cochée alors on créé un justificatif de même période"
|
|
|
|
justi: Justificatif = Justificatif.create_justificatif(
|
|
|
|
etudiant=etud,
|
|
|
|
date_debut=dt_debut_tz_server,
|
|
|
|
date_fin=dt_fin_tz_server,
|
|
|
|
etat=scu.EtatJustificatif.VALIDE,
|
|
|
|
user_id=current_user.id,
|
|
|
|
)
|
|
|
|
|
|
|
|
# On met à jour les assiduités en fonction du nouveau justificatif
|
|
|
|
compute_assiduites_justified(etud.id, [justi])
|
|
|
|
|
2024-01-08 19:01:54 +01:00
|
|
|
# Invalider cache
|
|
|
|
scass.simple_invalidate_cache(ass.to_dict(), etud.id)
|
|
|
|
|
2023-12-05 21:04:38 +01:00
|
|
|
return True
|
2023-12-06 02:04:10 +01:00
|
|
|
except ScoValueError as exc:
|
2023-12-14 20:50:27 +01:00
|
|
|
form.set_error(f"Erreur: {exc.args[0]}")
|
2023-12-06 02:04:10 +01:00
|
|
|
return False
|
2023-12-05 21:04:38 +01:00
|
|
|
|
2023-04-17 15:44:55 +02:00
|
|
|
|
2023-12-09 15:53:45 +01:00
|
|
|
@bp.route("/liste_assiduites_etud")
|
2023-04-20 18:04:21 +02:00
|
|
|
@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-11-24 18:07:30 +01:00
|
|
|
# Gestion d'une assiduité unique (redirigé depuis le calendrier) TODO-Assiduites
|
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-12-01 13:46:33 +01:00
|
|
|
tableau = _prepare_tableau(
|
|
|
|
liste_assi.AssiJustifData.from_etudiants(
|
2023-11-24 18:07:30 +01:00
|
|
|
etud,
|
|
|
|
),
|
2024-01-16 09:19:40 +01:00
|
|
|
filename=f"assiduites-justificatifs-{etud.id}",
|
2023-11-24 18:07:30 +01:00
|
|
|
afficher_etu=False,
|
2023-12-01 13:46:33 +01:00
|
|
|
filtre=liste_assi.AssiFiltre(type_obj=0),
|
|
|
|
options=liste_assi.AssiDisplayOptions(show_module=True),
|
2024-01-16 09:19:40 +01:00
|
|
|
cache_key=f"tableau-etud-{etud.id}",
|
2023-11-24 18:07:30 +01:00
|
|
|
)
|
|
|
|
if not tableau[0]:
|
|
|
|
return tableau[1]
|
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),
|
2023-09-11 15:55:18 +02:00
|
|
|
assi_id=assiduite_id,
|
2023-11-24 18:07:30 +01:00
|
|
|
tableau=tableau[1],
|
2023-04-20 18:04:21 +02:00
|
|
|
),
|
|
|
|
).build()
|
|
|
|
|
|
|
|
|
2023-12-07 22:10:51 +01:00
|
|
|
@bp.route("/bilan_etud")
|
2023-06-28 17:15:24 +02:00
|
|
|
@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-12-29 13:57:44 +01:00
|
|
|
date_debut = scu.date_debut_annee_scolaire().strftime("%d/%m/%Y")
|
|
|
|
date_fin: str = scu.date_fin_annee_scolaire().strftime("%d/%m/%Y")
|
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-12-22 15:31:30 +01:00
|
|
|
@bp.route("/edit_justificatif_etud/<int:justif_id>", methods=["GET", "POST"])
|
|
|
|
@scodoc
|
|
|
|
@permission_required(Permission.AbsChange)
|
|
|
|
def edit_justificatif_etud(justif_id: int):
|
|
|
|
"""
|
|
|
|
Edition d'un justificatif
|
|
|
|
Args:
|
|
|
|
justif_id (int): l'identifiant du justificatif
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
str: l'html généré
|
|
|
|
"""
|
|
|
|
justif = Justificatif.get_justificatif(justif_id)
|
|
|
|
form = AjoutJustificatifEtudForm(obj=justif)
|
|
|
|
# Set the default value for the etat field
|
|
|
|
if request.method == "GET":
|
|
|
|
form.date_debut.data = justif.date_debut.strftime("%d/%m/%Y")
|
|
|
|
form.date_fin.data = justif.date_fin.strftime("%d/%m/%Y")
|
|
|
|
if form.date_fin.data == form.date_debut.data:
|
|
|
|
# un seul jour: pas de date de fin, indique les heures
|
|
|
|
form.date_fin.data = ""
|
|
|
|
form.heure_debut.data = justif.date_debut.strftime("%H:%M")
|
|
|
|
form.heure_fin.data = justif.date_fin.strftime("%H:%M")
|
|
|
|
form.entry_date.data = (
|
|
|
|
justif.entry_date.strftime("%d/%m/%Y") if justif.entry_date else ""
|
|
|
|
)
|
|
|
|
form.etat.data = str(justif.etat)
|
|
|
|
|
|
|
|
redirect_url = url_for(
|
|
|
|
"assiduites.liste_assiduites_etud",
|
|
|
|
scodoc_dept=g.scodoc_dept,
|
|
|
|
etudid=justif.etudiant.id,
|
|
|
|
)
|
|
|
|
if form.validate_on_submit():
|
|
|
|
if _record_justificatif_etud(justif.etudiant, form, justif):
|
|
|
|
return redirect(redirect_url)
|
|
|
|
|
|
|
|
# Fichiers
|
|
|
|
filenames, nb_files = justif.get_fichiers()
|
|
|
|
|
|
|
|
return render_template(
|
|
|
|
"assiduites/pages/ajout_justificatif_etud.j2",
|
|
|
|
assi_limit_annee=sco_preferences.get_preference(
|
|
|
|
"assi_limit_annee",
|
|
|
|
dept_id=g.scodoc_dept_id,
|
|
|
|
),
|
|
|
|
etud=justif.etudiant,
|
|
|
|
filenames=filenames,
|
|
|
|
form=form,
|
|
|
|
justif=justif,
|
|
|
|
nb_files=nb_files,
|
|
|
|
page_title="Modification justificatif",
|
|
|
|
redirect_url=redirect_url,
|
|
|
|
sco=ScoData(justif.etudiant),
|
|
|
|
scu=scu,
|
|
|
|
)
|
|
|
|
|
|
|
|
|
2023-12-12 03:05:31 +01:00
|
|
|
@bp.route(
|
|
|
|
"/ajout_justificatif_etud", methods=["GET", "POST"]
|
|
|
|
) # was AjoutJustificatifEtud
|
2023-06-20 15:50:56 +02:00
|
|
|
@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():
|
|
|
|
"""
|
2023-12-22 15:31:30 +01:00
|
|
|
ajout_justificatif_etud : Affichage et création des justificatifs de l'étudiant
|
2023-06-20 15:50:56 +02:00
|
|
|
Args:
|
|
|
|
etudid (int): l'identifiant de l'étudiant
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
str: l'html généré
|
|
|
|
"""
|
2023-12-12 03:05:31 +01:00
|
|
|
etud = Identite.get_etud(request.args.get("etudid"))
|
|
|
|
redirect_url = url_for(
|
|
|
|
"assiduites.calendrier_assi_etud", scodoc_dept=g.scodoc_dept, etudid=etud.id
|
2023-06-20 15:50:56 +02:00
|
|
|
)
|
|
|
|
|
2023-12-12 03:05:31 +01:00
|
|
|
form = AjoutJustificatifEtudForm()
|
|
|
|
if form.validate_on_submit():
|
|
|
|
if form.cancel.data: # cancel button
|
|
|
|
return redirect(redirect_url)
|
|
|
|
ok = _record_justificatif_etud(etud, form)
|
|
|
|
if ok:
|
|
|
|
return redirect(redirect_url)
|
|
|
|
|
|
|
|
is_html, tableau = _prepare_tableau(
|
2023-12-01 13:46:33 +01:00
|
|
|
liste_assi.AssiJustifData.from_etudiants(
|
2023-11-24 18:07:30 +01:00
|
|
|
etud,
|
|
|
|
),
|
2023-12-12 03:05:31 +01:00
|
|
|
filename=f"justificatifs-{etud.nom or ''}",
|
2023-11-24 18:07:30 +01:00
|
|
|
afficher_etu=False,
|
2023-12-01 13:46:33 +01:00
|
|
|
filtre=liste_assi.AssiFiltre(type_obj=2),
|
|
|
|
options=liste_assi.AssiDisplayOptions(show_module=False, show_desc=True),
|
2023-11-24 18:07:30 +01:00
|
|
|
afficher_options=False,
|
2023-12-12 03:05:31 +01:00
|
|
|
titre="Justificatifs enregistrés pour cet étudiant",
|
2024-01-11 17:24:01 +01:00
|
|
|
cache_key=f"tableau-etud-{etud.id}",
|
2023-11-24 18:07:30 +01:00
|
|
|
)
|
2023-12-12 03:05:31 +01:00
|
|
|
if not is_html:
|
|
|
|
return tableau
|
2023-11-24 18:07:30 +01:00
|
|
|
|
2023-12-12 03:05:31 +01:00
|
|
|
return render_template(
|
|
|
|
"assiduites/pages/ajout_justificatif_etud.j2",
|
|
|
|
assi_limit_annee=sco_preferences.get_preference(
|
|
|
|
"assi_limit_annee",
|
|
|
|
dept_id=g.scodoc_dept_id,
|
2023-06-20 15:50:56 +02:00
|
|
|
),
|
2023-12-12 03:05:31 +01:00
|
|
|
etud=etud,
|
|
|
|
form=form,
|
|
|
|
page_title="Justificatifs",
|
|
|
|
redirect_url=redirect_url,
|
|
|
|
sco=ScoData(etud),
|
|
|
|
scu=scu,
|
|
|
|
tableau=tableau,
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
def _record_justificatif_etud(
|
2023-12-22 15:31:30 +01:00
|
|
|
etud: Identite, form: AjoutJustificatifEtudForm, justif: Justificatif | None = None
|
2023-12-12 03:05:31 +01:00
|
|
|
) -> bool:
|
|
|
|
"""Enregistre les données du formulaire de saisie justificatif (et ses fichiers).
|
|
|
|
Returns ok if successfully recorded, else put error info in the form.
|
|
|
|
Format attendu des données du formulaire:
|
|
|
|
form.assi_etat.data : 'absent'
|
|
|
|
form.date_debut.data : '05/12/2023'
|
|
|
|
form.heure_debut.data : '09:06' (heure locale du serveur)
|
2023-12-22 15:31:30 +01:00
|
|
|
Si justif, modifie le justif existant, sinon en crée un nouveau
|
2023-12-12 03:05:31 +01:00
|
|
|
"""
|
|
|
|
(
|
|
|
|
ok,
|
|
|
|
dt_debut_tz_server,
|
|
|
|
dt_fin_tz_server,
|
|
|
|
dt_entry_date_tz_server,
|
|
|
|
) = _get_dates_from_assi_form(form)
|
|
|
|
|
|
|
|
if not ok:
|
2023-12-22 15:31:30 +01:00
|
|
|
log("_record_justificatif_etud: dates invalides")
|
|
|
|
form.set_error("Erreur: dates invalides")
|
|
|
|
return False
|
|
|
|
if not form.etat.data:
|
|
|
|
log("_record_justificatif_etud: etat invalide")
|
|
|
|
form.set_error("Erreur: état invalide")
|
|
|
|
return False
|
|
|
|
etat = int(form.etat.data)
|
|
|
|
if not scu.EtatJustificatif.is_valid_etat(etat):
|
|
|
|
log(f"_record_justificatif_etud: etat invalide ({etat})")
|
|
|
|
form.set_error("Erreur: état invalide")
|
2023-12-12 03:05:31 +01:00
|
|
|
return False
|
2023-12-22 15:31:30 +01:00
|
|
|
|
2023-12-12 03:05:31 +01:00
|
|
|
try:
|
2023-12-22 15:31:30 +01:00
|
|
|
message = ""
|
|
|
|
if justif:
|
|
|
|
form.date_debut.data = dt_debut_tz_server
|
|
|
|
form.date_fin.data = dt_fin_tz_server
|
|
|
|
form.entry_date.data = dt_entry_date_tz_server
|
|
|
|
if justif.edit_from_form(form):
|
|
|
|
message = "Justificatif modifié"
|
|
|
|
else:
|
|
|
|
message = "Pas de modification"
|
|
|
|
else:
|
|
|
|
justif = Justificatif.create_justificatif(
|
|
|
|
etud,
|
|
|
|
dt_debut_tz_server,
|
|
|
|
dt_fin_tz_server,
|
|
|
|
etat=etat,
|
|
|
|
raison=form.raison.data,
|
|
|
|
entry_date=dt_entry_date_tz_server,
|
|
|
|
user_id=current_user.id,
|
|
|
|
)
|
|
|
|
message = "Justificatif créé"
|
|
|
|
db.session.add(justif)
|
|
|
|
if not _upload_justificatif_files(justif, form):
|
2023-12-12 20:19:18 +01:00
|
|
|
flash("Erreur enregistrement fichiers")
|
|
|
|
log("problem in _upload_justificatif_files, rolling back")
|
2023-12-12 03:05:31 +01:00
|
|
|
db.session.rollback()
|
|
|
|
return False
|
|
|
|
db.session.commit()
|
2023-12-22 15:31:30 +01:00
|
|
|
compute_assiduites_justified(etud.id, [justif])
|
|
|
|
scass.simple_invalidate_cache(justif.to_dict(), etud.id)
|
|
|
|
flash(message)
|
2023-12-12 03:05:31 +01:00
|
|
|
return True
|
|
|
|
except ScoValueError as exc:
|
2023-12-22 15:31:30 +01:00
|
|
|
log(f"_record_justificatif_etud: erreur {exc.args[0]}")
|
2023-12-12 03:05:31 +01:00
|
|
|
db.session.rollback()
|
2023-12-14 20:50:27 +01:00
|
|
|
form.set_error(f"Erreur: {exc.args[0]}")
|
2023-12-12 03:05:31 +01:00
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
|
|
def _upload_justificatif_files(
|
|
|
|
just: Justificatif, form: AjoutJustificatifEtudForm
|
|
|
|
) -> bool:
|
|
|
|
"""Enregistre les fichiers du formulaire de création de justificatif"""
|
|
|
|
# Utilisation de l'archiver de justificatifs
|
|
|
|
archiver: JustificatifArchiver = JustificatifArchiver()
|
|
|
|
archive_name: str = just.fichier
|
|
|
|
try:
|
|
|
|
# On essaye de sauvegarder les fichiers
|
|
|
|
for file in form.fichiers.data or []:
|
|
|
|
archive_name, _ = archiver.save_justificatif(
|
|
|
|
just.etudiant,
|
|
|
|
filename=file.filename,
|
|
|
|
data=file.stream.read(),
|
|
|
|
archive_name=archive_name,
|
|
|
|
user_id=current_user.id,
|
|
|
|
)
|
|
|
|
flash(f"Fichier {file.filename} enregistré")
|
|
|
|
if form.fichiers.data:
|
|
|
|
# On actualise l'archive du justificatif
|
|
|
|
just.fichier = archive_name
|
|
|
|
db.session.add(just)
|
2023-12-12 20:19:18 +01:00
|
|
|
db.session.commit()
|
2023-12-12 03:05:31 +01:00
|
|
|
return True
|
|
|
|
except ScoValueError as exc:
|
2023-12-12 13:58:07 +01:00
|
|
|
log(
|
|
|
|
f"_upload_justificatif_files: error on {file.filename} for etud {just.etudid}"
|
|
|
|
)
|
2023-12-14 20:50:27 +01:00
|
|
|
form.set_error(f"Erreur sur fichier justificatif: {exc.args[0]}")
|
2023-12-12 03:05:31 +01:00
|
|
|
return False
|
2023-06-20 15:50:56 +02:00
|
|
|
|
|
|
|
|
2023-12-12 03:05:31 +01:00
|
|
|
@bp.route("/calendrier_assi_etud")
|
2023-06-22 16:25:13 +02:00
|
|
|
@scodoc
|
|
|
|
@permission_required(Permission.ScoView)
|
2023-12-12 03:05:31 +01:00
|
|
|
def calendrier_assi_etud():
|
2023-06-22 16:25:13 +02:00
|
|
|
"""
|
2023-12-12 03:05:31 +01:00
|
|
|
Affichage d'un calendrier de l'assiduité de l'étudiant
|
2023-06-22 16:25:13 +02:00
|
|
|
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")
|
|
|
|
|
2024-01-11 17:19:56 +01:00
|
|
|
# Options
|
|
|
|
mode_demi: bool = scu.to_bool(request.args.get("mode_demi", "t"))
|
|
|
|
show_pres: bool = scu.to_bool(request.args.get("show_pres", "f"))
|
|
|
|
show_reta: bool = scu.to_bool(request.args.get("show_reta", "f"))
|
|
|
|
annee: int = int(request.args.get("annee", scu.annee_scolaire()))
|
2023-06-22 16:25:13 +02:00
|
|
|
|
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 += "]"
|
|
|
|
|
2024-01-16 16:15:48 +01:00
|
|
|
calendrier: dict[str, list["Jour"]] = generate_calendar(etud, annee)
|
2024-01-11 17:19:56 +01:00
|
|
|
|
2023-10-27 16:05:40 +02:00
|
|
|
# Peuplement du template jinja
|
2024-01-16 16:15:48 +01:00
|
|
|
return render_template(
|
|
|
|
"assiduites/pages/calendrier_assi_etud.j2",
|
|
|
|
sco=ScoData(etud),
|
|
|
|
annee=annee,
|
|
|
|
nonworkdays=_non_work_days(),
|
|
|
|
annees=annees_str,
|
|
|
|
calendrier=calendrier,
|
|
|
|
mode_demi=mode_demi,
|
|
|
|
show_pres=show_pres,
|
|
|
|
show_reta=show_reta,
|
|
|
|
)
|
2023-06-22 16:25:13 +02:00
|
|
|
|
|
|
|
|
2024-01-12 10:52:40 +01:00
|
|
|
@bp.route("/choix_date", methods=["GET", "POST"])
|
|
|
|
@scodoc
|
|
|
|
@permission_required(Permission.AbsChange)
|
|
|
|
def choix_date() -> str:
|
|
|
|
formsemestre_id = request.args.get("formsemestre_id")
|
|
|
|
formsemestre: FormSemestre = FormSemestre.query.get_or_404(formsemestre_id)
|
|
|
|
|
|
|
|
group_ids = request.args.get("group_ids")
|
|
|
|
moduleimpl_id = request.args.get("moduleimpl_id")
|
|
|
|
form = ChoixDateForm(request.form)
|
|
|
|
|
|
|
|
if form.validate_on_submit():
|
|
|
|
if form.cancel.data:
|
|
|
|
return redirect(url_for("scodoc.index"))
|
|
|
|
# Vérifier si date dans semestre
|
|
|
|
ok: bool = False
|
|
|
|
try:
|
|
|
|
date: datetime.date = datetime.datetime.strptime(
|
|
|
|
form.date.data, "%d/%m/%Y"
|
|
|
|
).date()
|
|
|
|
if date < formsemestre.date_debut or date > formsemestre.date_fin:
|
|
|
|
form.set_error(
|
|
|
|
"La date sélectionnée n'est pas dans le semestre.", form.date
|
|
|
|
)
|
|
|
|
else:
|
|
|
|
ok = True
|
|
|
|
except ValueError:
|
|
|
|
form.set_error("Date invalide", form.date)
|
|
|
|
|
|
|
|
if ok:
|
|
|
|
return redirect(
|
|
|
|
url_for(
|
2024-01-16 16:15:48 +01:00
|
|
|
"assiduites.signal_assiduites_group"
|
|
|
|
if request.args.get("readonly") is None
|
|
|
|
else "assiduites.visu_assiduites_group",
|
2024-01-12 10:52:40 +01:00
|
|
|
scodoc_dept=g.scodoc_dept,
|
|
|
|
formsemestre_id=formsemestre_id,
|
|
|
|
group_ids=group_ids,
|
|
|
|
moduleimpl_id=moduleimpl_id,
|
|
|
|
jour=date.isoformat(),
|
|
|
|
)
|
|
|
|
)
|
|
|
|
|
|
|
|
return render_template(
|
|
|
|
"assiduites/pages/choix_date.j2",
|
|
|
|
form=form,
|
|
|
|
sco=ScoData(formsemestre=formsemestre),
|
|
|
|
deb=formsemestre.date_debut.strftime("%d/%m/%Y"),
|
|
|
|
fin=formsemestre.date_fin.strftime("%d/%m/%Y"),
|
|
|
|
)
|
|
|
|
|
|
|
|
|
2023-12-31 22:18:13 +01:00
|
|
|
@bp.route("/signal_assiduites_group")
|
2023-04-17 15:44:55 +02:00
|
|
|
@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():
|
|
|
|
"""
|
2024-01-17 21:58:45 +01:00
|
|
|
signal_assiduites_group Saisie des assiduités des groupes pour le jour donné
|
2023-04-17 15:44:55 +02:00
|
|
|
|
|
|
|
Returns:
|
|
|
|
str: l'html généré
|
|
|
|
"""
|
2023-10-27 16:05:40 +02:00
|
|
|
# Récupération des paramètres de l'url
|
2024-01-17 21:58:45 +01:00
|
|
|
# formsemestre_id est optionnel si modimpl est indiqué
|
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
|
2024-01-17 23:51:45 +01:00
|
|
|
if moduleimpl_id is not None and moduleimpl_id >= 0:
|
2024-01-17 21:58:45 +01:00
|
|
|
modimpl = ModuleImpl.get_modimpl(moduleimpl_id)
|
|
|
|
else:
|
|
|
|
modimpl = None
|
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
|
|
|
|
|
2024-01-17 21:58:45 +01:00
|
|
|
if (formsemestre_id < 0 or formsemestre_id is None) and modimpl:
|
|
|
|
# si le module est spécifié mais pas le semestre:
|
|
|
|
formsemestre_id = modimpl.formsemestre_id
|
|
|
|
|
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(
|
2023-12-31 22:18:13 +01:00
|
|
|
group_ids,
|
|
|
|
moduleimpl_id=moduleimpl_id,
|
|
|
|
formsemestre_id=formsemestre_id,
|
|
|
|
select_all_when_unspecified=True,
|
2023-07-26 16:43:49 +02:00
|
|
|
)
|
|
|
|
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:
|
2024-01-12 10:52:40 +01:00
|
|
|
# Si le jour est hors semestre, renvoyer vers choix date
|
|
|
|
return redirect(
|
|
|
|
url_for(
|
|
|
|
"assiduites.choix_date",
|
|
|
|
formsemestre_id=formsemestre_id,
|
|
|
|
group_ids=group_ids,
|
|
|
|
moduleimpl_id=moduleimpl_id,
|
|
|
|
scodoc_dept=g.scodoc_dept,
|
|
|
|
)
|
2023-09-13 08:59:54 +02:00
|
|
|
)
|
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",
|
2024-01-16 16:15:48 +01:00
|
|
|
"css/minitimeline.css",
|
2023-07-26 16:43:49 +02:00
|
|
|
],
|
|
|
|
)
|
|
|
|
|
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()
|
|
|
|
|
|
|
|
|
2024-01-12 11:36:00 +01:00
|
|
|
@bp.route("/visu_assiduites_group")
|
2023-07-26 16:43:49 +02:00
|
|
|
@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()
|
2024-01-16 16:15:48 +01:00
|
|
|
if real_date < formsemestre.date_debut or real_date > formsemestre.date_fin:
|
|
|
|
# Si le jour est hors semestre, renvoyer vers choix date
|
|
|
|
return redirect(
|
|
|
|
url_for(
|
|
|
|
"assiduites.choix_date",
|
|
|
|
formsemestre_id=formsemestre_id,
|
|
|
|
group_ids=group_ids,
|
|
|
|
moduleimpl_id=moduleimpl_id,
|
|
|
|
scodoc_dept=g.scodoc_dept,
|
|
|
|
readonly="true",
|
|
|
|
)
|
|
|
|
)
|
2023-06-12 17:54:30 +02:00
|
|
|
|
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",
|
2024-01-16 16:15:48 +01:00
|
|
|
"css/minitimeline.css",
|
2023-04-17 15:44:55 +02:00
|
|
|
],
|
|
|
|
)
|
|
|
|
|
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-12-09 15:53:45 +01:00
|
|
|
class RowEtudWithAssi(RowEtud):
|
|
|
|
"""Ligne de la table d'étudiants avec colonne Assiduité"""
|
|
|
|
|
|
|
|
def __init__(
|
|
|
|
self,
|
|
|
|
table: TableEtud,
|
|
|
|
etud: Identite,
|
|
|
|
etat_assiduite: str,
|
2024-01-18 09:36:38 +01:00
|
|
|
est_just: bool,
|
2023-12-09 15:53:45 +01:00
|
|
|
*args,
|
|
|
|
**kwargs,
|
|
|
|
):
|
|
|
|
super().__init__(table, etud, *args, **kwargs)
|
|
|
|
self.etat_assiduite = etat_assiduite
|
2024-01-18 09:36:38 +01:00
|
|
|
self.est_just = est_just
|
2023-12-09 15:53:45 +01:00
|
|
|
# remplace lien vers fiche par lien vers calendrier
|
|
|
|
self.target_url = url_for(
|
2023-12-12 03:05:31 +01:00
|
|
|
"assiduites.calendrier_assi_etud", scodoc_dept=g.scodoc_dept, etudid=etud.id
|
2023-12-09 15:53:45 +01:00
|
|
|
)
|
|
|
|
self.target_title = f"Calendrier de {etud.nomprenom}"
|
|
|
|
|
|
|
|
def add_etud_cols(self):
|
|
|
|
"""Ajoute colonnes pour cet étudiant"""
|
|
|
|
super().add_etud_cols()
|
|
|
|
self.add_cell(
|
|
|
|
"assi-type",
|
|
|
|
"Présence",
|
|
|
|
self.etat_assiduite,
|
|
|
|
"assi-type",
|
|
|
|
)
|
|
|
|
self.classes += ["row-assiduite", self.etat_assiduite.lower()]
|
|
|
|
|
2024-01-18 09:36:38 +01:00
|
|
|
if self.est_just:
|
|
|
|
self.classes += ["justifiee"]
|
|
|
|
|
2023-12-09 15:53:45 +01:00
|
|
|
|
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-12-09 15:53:45 +01:00
|
|
|
"""Tableau de l'état d'assiduité d'un ou plusieurs groupes
|
|
|
|
sur la plage de dates date_debut, date_fin.
|
2023-12-09 16:03:17 +01:00
|
|
|
group_ids : ids de(s) groupe(s)
|
2023-12-09 15:53:45 +01:00
|
|
|
date_debut, date_fin: format ISO
|
|
|
|
evaluation_id: optionnel, évaluation concernée, pour titre et liens.
|
|
|
|
date_debut, date_fin en ISO
|
2023-12-09 16:03:17 +01:00
|
|
|
fmt : format export (xls, défaut html)
|
2023-12-09 15:53:45 +01:00
|
|
|
"""
|
2023-10-27 16:05:40 +02:00
|
|
|
|
2023-12-09 15:53:45 +01:00
|
|
|
# Récupération des paramètres 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")
|
2023-12-09 16:03:17 +01:00
|
|
|
fmt = request.args.get("fmt", "html")
|
2023-12-09 15:53:45 +01:00
|
|
|
group_ids = request.args.getlist("group_ids", int)
|
|
|
|
evaluation_id = request.args.get("evaluation_id")
|
|
|
|
evaluation: Evaluation = (
|
|
|
|
Evaluation.query.get_or_404(evaluation_id)
|
|
|
|
if evaluation_id is not None
|
|
|
|
else 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
|
|
|
|
2023-12-09 15:53:45 +01:00
|
|
|
# Les groupes:
|
|
|
|
groups = [GroupDescr.query.get_or_404(group_id) for group_id in group_ids]
|
|
|
|
# Les étudiants de tous les groupes sélectionnés, flat list
|
2023-05-30 17:16:07 +02:00
|
|
|
etuds = [
|
2023-12-09 15:53:45 +01:00
|
|
|
etud for gr_etuds in [group.etuds for group in groups] for etud in gr_etuds
|
2023-05-30 17:16:07 +02:00
|
|
|
]
|
2023-12-09 15:53:45 +01:00
|
|
|
|
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(
|
2023-12-09 15:53:45 +01:00
|
|
|
Assiduite.etudid.in_([etud.id for etud in etuds])
|
2023-05-30 17:16:07 +02:00
|
|
|
)
|
2023-10-27 16:05:40 +02:00
|
|
|
# Filtrage des assiduités en fonction des dates données
|
2024-01-18 09:36:38 +01:00
|
|
|
assiduites = scass.filter_by_date(assiduites, Assiduite, date_debut, date_fin)
|
2023-05-30 17:16:07 +02:00
|
|
|
|
2023-12-09 15:53:45 +01:00
|
|
|
# Génération table
|
|
|
|
table = TableEtud(row_class=RowEtudWithAssi)
|
|
|
|
for etud in sorted(etuds, key=lambda e: e.sort_key):
|
2023-10-27 16:05:40 +02:00
|
|
|
# On récupère l'état de la première assiduité sur la période
|
2023-12-09 15:53:45 +01:00
|
|
|
assi = assiduites.filter_by(etudid=etud.id).first()
|
2023-05-30 17:16:07 +02:00
|
|
|
etat = ""
|
2023-12-06 17:13:44 +01:00
|
|
|
if assi is not None and assi.etat != scu.EtatAssiduite.PRESENT:
|
2023-05-30 17:16:07 +02:00
|
|
|
etat = scu.EtatAssiduite.inverse().get(assi.etat).name
|
2024-01-18 09:36:38 +01:00
|
|
|
row = table.row_class(table, etud, etat, assi.est_just)
|
2023-12-09 15:53:45 +01:00
|
|
|
row.add_etud_cols()
|
|
|
|
table.add_row(row)
|
2023-05-30 17:16:07 +02:00
|
|
|
|
2023-12-09 16:03:17 +01:00
|
|
|
if fmt.startswith("xls"):
|
|
|
|
return scu.send_file(
|
|
|
|
table.excel(),
|
|
|
|
filename=f"assiduite-eval-{date_debut.isoformat()}",
|
|
|
|
mime=scu.XLSX_MIMETYPE,
|
|
|
|
suffix=scu.XLSX_SUFFIX,
|
|
|
|
)
|
2023-12-09 15:53:45 +01:00
|
|
|
return render_template(
|
|
|
|
"assiduites/pages/etat_abs_date.j2",
|
|
|
|
date_debut=date_debut,
|
|
|
|
date_fin=date_fin,
|
|
|
|
evaluation=evaluation,
|
|
|
|
etuds=etuds,
|
|
|
|
group_title=", ".join(gr.get_nom_with_part("tous") for gr in groups),
|
|
|
|
sco=ScoData(),
|
|
|
|
table=table,
|
2023-05-30 17:16:07 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
|
2023-12-15 01:31:15 +01:00
|
|
|
@bp.route("/visu_assi_group")
|
2023-07-10 17:38:50 +02:00
|
|
|
@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(
|
2023-12-15 01:31:15 +01:00
|
|
|
"assiduites/pages/visu_assi_group.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-12-01 13:46:33 +01:00
|
|
|
def _prepare_tableau(
|
|
|
|
data: liste_assi.AssiJustifData,
|
2023-11-24 13:58:03 +01:00
|
|
|
filename: str = "tableau-assiduites",
|
|
|
|
afficher_etu: bool = True,
|
2023-12-01 13:46:33 +01:00
|
|
|
filtre: liste_assi.AssiFiltre = None,
|
|
|
|
options: liste_assi.AssiDisplayOptions = None,
|
2023-11-24 18:07:30 +01:00
|
|
|
afficher_options: bool = True,
|
2023-12-12 03:05:31 +01:00
|
|
|
titre="Évènements enregistrés pour cet étudiant",
|
2024-01-11 17:24:01 +01:00
|
|
|
cache_key: str = "",
|
2023-12-01 13:46:33 +01:00
|
|
|
) -> tuple[bool, Response | str]:
|
2023-11-24 13:58:03 +01:00
|
|
|
"""
|
2023-12-01 13:46:33 +01:00
|
|
|
Prépare un tableau d'assiduités / justificatifs
|
2023-11-22 16:49:13 +01:00
|
|
|
|
2023-12-01 13:46:33 +01:00
|
|
|
Cette fonction récupère dans la requête les arguments :
|
2023-11-24 13:58:03 +01:00
|
|
|
|
|
|
|
show_pres : bool -> Affiche les présences, par défaut False
|
|
|
|
show_reta : bool -> Affiche les retard, par défaut False
|
|
|
|
show_desc : bool -> Affiche les descriptions, par défaut False
|
|
|
|
|
|
|
|
Returns:
|
2023-12-01 13:46:33 +01:00
|
|
|
tuple[bool | Reponse|str ]:
|
2023-11-24 13:58:03 +01:00
|
|
|
- bool : Vrai si la réponse est du Text/HTML
|
|
|
|
- Reponse : du Text/HTML ou Une Reponse (téléchargement fichier)
|
|
|
|
"""
|
2023-11-22 16:49:13 +01:00
|
|
|
|
|
|
|
show_pres: bool | str = request.args.get("show_pres", False)
|
|
|
|
show_reta: bool | str = request.args.get("show_reta", False)
|
2023-11-24 13:58:03 +01:00
|
|
|
show_desc: bool | str = request.args.get("show_desc", False)
|
2023-10-26 13:12:22 +02:00
|
|
|
|
2023-11-22 16:49:13 +01:00
|
|
|
nb_ligne_page: int = request.args.get("nb_ligne_page")
|
|
|
|
# Vérification de nb_ligne_page
|
|
|
|
try:
|
|
|
|
nb_ligne_page: int = int(nb_ligne_page)
|
|
|
|
except (ValueError, TypeError):
|
2023-11-24 13:58:03 +01:00
|
|
|
nb_ligne_page = liste_assi.ListeAssiJusti.NB_PAR_PAGE
|
2023-11-22 16:49:13 +01:00
|
|
|
|
|
|
|
page_number: int = request.args.get("n_page", 1)
|
|
|
|
# Vérification de page_number
|
|
|
|
try:
|
|
|
|
page_number: int = int(page_number)
|
|
|
|
except (ValueError, TypeError):
|
|
|
|
page_number = 1
|
2023-11-03 14:57:22 +01:00
|
|
|
|
2023-11-24 13:58:03 +01:00
|
|
|
fmt = request.args.get("fmt", "html")
|
|
|
|
|
2024-01-11 17:24:01 +01:00
|
|
|
# Ordre
|
|
|
|
ordre: tuple[str, str | bool] = None
|
|
|
|
ordre_col: str = request.args.get("order_col", None)
|
|
|
|
ordre_tri: str = request.args.get("order", None)
|
|
|
|
if ordre_col is not None and ordre_tri is not None:
|
|
|
|
ordre = (ordre_col, ordre_tri == "ascending")
|
|
|
|
|
2023-11-24 13:58:03 +01:00
|
|
|
if options is None:
|
2023-12-01 13:46:33 +01:00
|
|
|
options: liste_assi.AssiDisplayOptions = liste_assi.AssiDisplayOptions()
|
2023-11-24 13:58:03 +01:00
|
|
|
|
|
|
|
options.remplacer(
|
|
|
|
page=page_number,
|
|
|
|
nb_ligne_page=nb_ligne_page,
|
|
|
|
show_pres=show_pres,
|
|
|
|
show_reta=show_reta,
|
|
|
|
show_desc=show_desc,
|
|
|
|
show_etu=afficher_etu,
|
2024-01-11 17:24:01 +01:00
|
|
|
order=ordre,
|
2023-11-24 13:58:03 +01:00
|
|
|
)
|
2023-10-26 13:12:22 +02:00
|
|
|
|
2023-11-24 13:58:03 +01:00
|
|
|
table: liste_assi.ListeAssiJusti = liste_assi.ListeAssiJusti(
|
2023-11-24 18:07:30 +01:00
|
|
|
table_data=data,
|
2023-11-24 13:58:03 +01:00
|
|
|
options=options,
|
|
|
|
filtre=filtre,
|
2024-01-11 17:24:01 +01:00
|
|
|
no_pagination=fmt.startswith("xls"),
|
|
|
|
titre=cache_key,
|
2023-11-22 16:49:13 +01:00
|
|
|
)
|
2023-10-26 13:12:22 +02:00
|
|
|
|
|
|
|
if fmt.startswith("xls"):
|
2023-11-24 13:58:03 +01:00
|
|
|
return False, scu.send_file(
|
2023-10-26 13:12:22 +02:00
|
|
|
table.excel(),
|
2023-11-24 13:58:03 +01:00
|
|
|
filename=filename,
|
2023-10-26 13:12:22 +02:00
|
|
|
mime=scu.XLSX_MIMETYPE,
|
|
|
|
suffix=scu.XLSX_SUFFIX,
|
|
|
|
)
|
|
|
|
|
2023-11-24 13:58:03 +01:00
|
|
|
return True, render_template(
|
|
|
|
"assiduites/widgets/tableau.j2",
|
2023-12-01 13:46:33 +01:00
|
|
|
table=table,
|
2023-11-22 16:49:13 +01:00
|
|
|
total_pages=table.total_pages,
|
2023-11-24 13:58:03 +01:00
|
|
|
options=options,
|
2023-11-24 18:07:30 +01:00
|
|
|
afficher_options=afficher_options,
|
2023-12-12 03:05:31 +01:00
|
|
|
titre=titre,
|
2023-10-26 13:12:22 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
|
2023-12-07 22:10:51 +01:00
|
|
|
@bp.route("/tableau_assiduite_actions", methods=["GET", "POST"])
|
2023-11-24 13:58:03 +01:00
|
|
|
@scodoc
|
|
|
|
@permission_required(Permission.AbsChange)
|
|
|
|
def tableau_assiduite_actions():
|
2023-12-06 14:42:10 +01:00
|
|
|
"""Edition/suppression/information sur une assiduité ou un justificatif
|
|
|
|
type = "assiduite" | "justificatif"
|
2024-01-08 19:06:44 +01:00
|
|
|
action = "supprimer" | "details" | "justifier"
|
2023-12-06 14:42:10 +01:00
|
|
|
"""
|
2023-11-24 13:58:03 +01:00
|
|
|
obj_type: str = request.args.get("type", "assiduite")
|
|
|
|
action: str = request.args.get("action", "details")
|
|
|
|
obj_id: str = int(request.args.get("obj_id", -1))
|
|
|
|
|
|
|
|
objet: Assiduite | Justificatif
|
2023-12-06 14:42:10 +01:00
|
|
|
objet_name = ""
|
2023-11-24 13:58:03 +01:00
|
|
|
if obj_type == "assiduite":
|
|
|
|
objet: Assiduite = Assiduite.query.get_or_404(obj_id)
|
2023-12-06 14:42:10 +01:00
|
|
|
objet_name = scu.EtatAssiduite(objet.etat).version_lisible()
|
2023-11-24 13:58:03 +01:00
|
|
|
else:
|
|
|
|
objet: Justificatif = Justificatif.query.get_or_404(obj_id)
|
2023-12-06 14:42:10 +01:00
|
|
|
objet_name = "Justificatif"
|
2023-11-24 13:58:03 +01:00
|
|
|
|
2023-12-06 14:42:10 +01:00
|
|
|
# Suppression : attention, POST ou GET !
|
2023-11-24 13:58:03 +01:00
|
|
|
if action == "supprimer":
|
2023-12-07 12:38:47 +01:00
|
|
|
objet.supprime()
|
2023-12-06 14:42:10 +01:00
|
|
|
flash(f"{objet_name} supprimé")
|
2023-11-24 13:58:03 +01:00
|
|
|
|
|
|
|
return redirect(request.referrer)
|
|
|
|
|
2024-01-08 19:06:44 +01:00
|
|
|
# Justification d'une assiduité depuis le tableau
|
|
|
|
if action == "justifier" and obj_type == "assiduite":
|
|
|
|
# Création du justificatif correspondant
|
|
|
|
justificatif_correspondant: Justificatif = Justificatif.create_justificatif(
|
|
|
|
etudiant=objet.etudiant,
|
|
|
|
date_debut=objet.date_debut,
|
|
|
|
date_fin=objet.date_fin,
|
|
|
|
etat=scu.EtatJustificatif.VALIDE,
|
|
|
|
user_id=current_user.id,
|
|
|
|
)
|
|
|
|
|
|
|
|
compute_assiduites_justified(objet.etudiant.id, [justificatif_correspondant])
|
2024-01-09 14:38:02 +01:00
|
|
|
scass.simple_invalidate_cache(
|
|
|
|
justificatif_correspondant.to_dict(), objet.etudiant.id
|
|
|
|
)
|
2024-01-08 19:06:44 +01:00
|
|
|
flash(f"{objet_name} justifiée")
|
|
|
|
return redirect(request.referrer)
|
|
|
|
|
2023-11-24 13:58:03 +01:00
|
|
|
if request.method == "GET":
|
2023-12-06 14:42:10 +01:00
|
|
|
module: str | int = "" # moduleimpl_id ou chaine libre
|
2023-11-24 13:58:03 +01:00
|
|
|
|
|
|
|
if obj_type == "assiduite":
|
2023-12-06 14:42:10 +01:00
|
|
|
# Construction du menu module
|
|
|
|
# XXX ca ne va pas car cela ne prend qu'un semestre
|
|
|
|
# TODO reprendre le menu de la page ajout_assiduite_etud
|
2023-11-24 13:58:03 +01:00
|
|
|
formsemestre = objet.get_formsemestre()
|
2023-12-12 23:24:37 +01:00
|
|
|
if formsemestre:
|
|
|
|
if objet.moduleimpl_id is not None:
|
|
|
|
module = objet.moduleimpl_id
|
|
|
|
elif objet.external_data is not None:
|
|
|
|
module = objet.external_data.get("module", "")
|
|
|
|
module = module.lower() if isinstance(module, str) else module
|
|
|
|
module = _module_selector(formsemestre, module)
|
|
|
|
else:
|
|
|
|
module = "pas de semestre correspondant"
|
2023-11-24 13:58:03 +01:00
|
|
|
|
|
|
|
return render_template(
|
2023-12-07 22:10:51 +01:00
|
|
|
"assiduites/pages/tableau_assiduite_actions.j2",
|
2023-11-24 13:58:03 +01:00
|
|
|
sco=ScoData(etud=objet.etudiant),
|
2024-01-18 17:05:43 +01:00
|
|
|
# type utilisé dans les actions modifier / détails (modifier.j2, details.j2)
|
2023-11-24 13:58:03 +01:00
|
|
|
type="Justificatif" if obj_type == "justificatif" else "Assiduité",
|
|
|
|
action=action,
|
2023-12-06 14:42:10 +01:00
|
|
|
etud=objet.etudiant,
|
2023-11-24 13:58:03 +01:00
|
|
|
objet=_preparer_objet(obj_type, objet),
|
2023-12-06 14:42:10 +01:00
|
|
|
objet_name=objet_name,
|
2023-11-24 13:58:03 +01:00
|
|
|
obj_id=obj_id,
|
|
|
|
moduleimpl=module,
|
|
|
|
)
|
2023-12-06 14:42:10 +01:00
|
|
|
# ----- Cas POST
|
2023-11-24 18:07:30 +01:00
|
|
|
if obj_type == "assiduite":
|
|
|
|
try:
|
|
|
|
_action_modifier_assiduite(objet)
|
|
|
|
except ScoValueError as error:
|
|
|
|
raise ScoValueError(error.args[0], request.referrer) from error
|
|
|
|
flash("L'assiduité a bien été modifiée.")
|
|
|
|
else:
|
|
|
|
try:
|
|
|
|
_action_modifier_justificatif(objet)
|
|
|
|
except ScoValueError as error:
|
|
|
|
raise ScoValueError(error.args[0], request.referrer) from error
|
|
|
|
flash("Le justificatif a bien été modifié.")
|
|
|
|
return redirect(request.form["table_url"])
|
|
|
|
|
|
|
|
|
|
|
|
def _action_modifier_assiduite(assi: Assiduite):
|
|
|
|
form = request.form
|
|
|
|
|
|
|
|
# Gestion de l'état
|
|
|
|
etat = scu.EtatAssiduite.get(form["etat"])
|
|
|
|
if etat is not None:
|
|
|
|
assi.etat = etat
|
|
|
|
if etat == scu.EtatAssiduite.PRESENT:
|
|
|
|
assi.est_just = False
|
|
|
|
else:
|
|
|
|
assi.est_just = len(get_assiduites_justif(assi.assiduite_id, False)) > 0
|
|
|
|
|
|
|
|
# Gestion de la description
|
|
|
|
assi.description = form["description"]
|
|
|
|
|
2024-01-12 10:00:53 +01:00
|
|
|
possible_moduleimpl_id: str = form["moduleimpl_select"]
|
2024-01-12 10:52:40 +01:00
|
|
|
|
2024-01-12 10:00:53 +01:00
|
|
|
# Raise ScoValueError (si None et force module | Etudiant non inscrit | Module non reconnu)
|
|
|
|
assi.set_moduleimpl(possible_moduleimpl_id)
|
2023-11-24 18:07:30 +01:00
|
|
|
|
|
|
|
db.session.add(assi)
|
|
|
|
db.session.commit()
|
|
|
|
scass.simple_invalidate_cache(assi.to_dict(True), assi.etudid)
|
|
|
|
|
|
|
|
|
|
|
|
def _action_modifier_justificatif(justi: Justificatif):
|
2023-12-22 15:31:30 +01:00
|
|
|
"Modifie le justificatif avec les valeurs dans le form"
|
2023-11-24 18:07:30 +01:00
|
|
|
form = request.form
|
|
|
|
|
|
|
|
# Gestion des Dates
|
|
|
|
date_debut: datetime = scu.is_iso_formated(form["date_debut"], True)
|
|
|
|
date_fin: datetime = scu.is_iso_formated(form["date_fin"], True)
|
|
|
|
if date_debut is None or date_fin is None or date_fin < date_debut:
|
|
|
|
raise ScoValueError("Dates invalides", request.referrer)
|
|
|
|
justi.date_debut = date_debut
|
|
|
|
justi.date_fin = date_fin
|
|
|
|
|
|
|
|
# Gestion de l'état
|
|
|
|
etat = scu.EtatJustificatif.get(form["etat"])
|
|
|
|
if etat is not None:
|
|
|
|
justi.etat = etat
|
|
|
|
else:
|
|
|
|
raise ScoValueError("État invalide", request.referrer)
|
|
|
|
|
|
|
|
# Gestion de la raison
|
|
|
|
justi.raison = form["raison"]
|
|
|
|
|
|
|
|
# Gestion des fichiers
|
|
|
|
files = request.files.getlist("justi_fich")
|
|
|
|
if len(files) != 0:
|
|
|
|
files = request.files.values()
|
|
|
|
|
|
|
|
archive_name: str = justi.fichier
|
|
|
|
# Utilisation de l'archiver de justificatifs
|
|
|
|
archiver: JustificatifArchiver = JustificatifArchiver()
|
|
|
|
|
|
|
|
for fich in files:
|
|
|
|
archive_name, _ = archiver.save_justificatif(
|
|
|
|
justi.etudiant,
|
|
|
|
filename=fich.filename,
|
|
|
|
data=fich.stream.read(),
|
|
|
|
archive_name=archive_name,
|
|
|
|
user_id=current_user.id,
|
|
|
|
)
|
|
|
|
|
|
|
|
justi.fichier = archive_name
|
|
|
|
|
|
|
|
db.session.add(justi)
|
|
|
|
db.session.commit()
|
|
|
|
scass.compute_assiduites_justified(justi.etudid, reset=True)
|
|
|
|
scass.simple_invalidate_cache(justi.to_dict(True), justi.etudid)
|
2023-11-24 13:58:03 +01:00
|
|
|
|
|
|
|
|
|
|
|
def _preparer_objet(
|
|
|
|
obj_type: str, objet: Assiduite | Justificatif, sans_gros_objet: bool = False
|
|
|
|
) -> dict:
|
2023-12-06 14:42:10 +01:00
|
|
|
"Préparation d'un objet pour simplifier l'affichage jinja"
|
2023-11-24 13:58:03 +01:00
|
|
|
objet_prepare: dict = objet.to_dict()
|
|
|
|
if obj_type == "assiduite":
|
|
|
|
objet_prepare["etat"] = (
|
|
|
|
scu.EtatAssiduite(objet.etat).version_lisible().capitalize()
|
|
|
|
)
|
|
|
|
objet_prepare["real_etat"] = scu.EtatAssiduite(objet.etat).name.lower()
|
|
|
|
objet_prepare["description"] = (
|
|
|
|
"" if objet.description is None else objet.description
|
|
|
|
)
|
|
|
|
objet_prepare["description"] = objet_prepare["description"].strip()
|
|
|
|
|
|
|
|
# Gestion du module
|
|
|
|
objet_prepare["module"] = objet.get_module(True)
|
|
|
|
|
|
|
|
# Gestion justification
|
|
|
|
|
|
|
|
if not objet.est_just:
|
|
|
|
objet_prepare["justification"] = {"est_just": False}
|
|
|
|
else:
|
|
|
|
objet_prepare["justification"] = {"est_just": True, "justificatifs": []}
|
|
|
|
|
|
|
|
if not sans_gros_objet:
|
|
|
|
justificatifs: list[int] = get_assiduites_justif(
|
|
|
|
objet.assiduite_id, False
|
|
|
|
)
|
|
|
|
for justi_id in justificatifs:
|
|
|
|
justi: Justificatif = Justificatif.query.get(justi_id)
|
|
|
|
objet_prepare["justification"]["justificatifs"].append(
|
|
|
|
_preparer_objet("justificatif", justi, sans_gros_objet=True)
|
|
|
|
)
|
|
|
|
|
2023-12-22 15:31:30 +01:00
|
|
|
else: # objet == "justificatif"
|
|
|
|
justif: Justificatif = objet
|
2023-11-24 13:58:03 +01:00
|
|
|
objet_prepare["etat"] = (
|
2023-12-22 15:31:30 +01:00
|
|
|
scu.EtatJustificatif(justif.etat).version_lisible().capitalize()
|
2023-11-24 13:58:03 +01:00
|
|
|
)
|
2023-12-22 15:31:30 +01:00
|
|
|
objet_prepare["real_etat"] = scu.EtatJustificatif(justif.etat).name.lower()
|
|
|
|
objet_prepare["raison"] = "" if justif.raison is None else justif.raison
|
2023-11-24 13:58:03 +01:00
|
|
|
objet_prepare["raison"] = objet_prepare["raison"].strip()
|
|
|
|
|
|
|
|
objet_prepare["justification"] = {"assiduites": [], "fichiers": {}}
|
|
|
|
if not sans_gros_objet:
|
2023-12-22 15:31:30 +01:00
|
|
|
assiduites: list[int] = scass.justifies(justif)
|
2023-11-24 13:58:03 +01:00
|
|
|
for assi_id in assiduites:
|
|
|
|
assi: Assiduite = Assiduite.query.get(assi_id)
|
|
|
|
objet_prepare["justification"]["assiduites"].append(
|
|
|
|
_preparer_objet("assiduite", assi, sans_gros_objet=True)
|
|
|
|
)
|
|
|
|
|
2023-12-22 15:31:30 +01:00
|
|
|
# fichiers justificatifs archivés:
|
|
|
|
filenames, nb_files = justif.get_fichiers()
|
2023-11-24 13:58:03 +01:00
|
|
|
objet_prepare["justification"]["fichiers"] = {
|
2023-12-22 15:31:30 +01:00
|
|
|
"total": nb_files,
|
|
|
|
"filenames": filenames,
|
2023-11-24 13:58:03 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
objet_prepare["date_fin"] = objet.date_fin.strftime("%d/%m/%y à %H:%M")
|
|
|
|
objet_prepare["real_date_fin"] = objet.date_fin.isoformat()
|
|
|
|
objet_prepare["date_debut"] = objet.date_debut.strftime("%d/%m/%y à %H:%M")
|
|
|
|
objet_prepare["real_date_debut"] = objet.date_debut.isoformat()
|
|
|
|
|
|
|
|
objet_prepare["entry_date"] = objet.entry_date.strftime("%d/%m/%y à %H:%M")
|
|
|
|
|
|
|
|
objet_prepare["etud_nom"] = objet.etudiant.nomprenom
|
|
|
|
|
2023-12-22 15:31:30 +01:00
|
|
|
if objet.user_id is not None:
|
2023-11-24 13:58:03 +01:00
|
|
|
user: User = User.query.get(objet.user_id)
|
|
|
|
objet_prepare["saisie_par"] = user.get_nomprenom()
|
|
|
|
else:
|
|
|
|
objet_prepare["saisie_par"] = "Inconnu"
|
|
|
|
|
|
|
|
return objet_prepare
|
|
|
|
|
|
|
|
|
2023-12-31 22:18:13 +01:00
|
|
|
@bp.route("/signal_assiduites_diff")
|
2023-06-01 17:32:50 +02:00
|
|
|
@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-12-31 22:18:13 +01:00
|
|
|
"""TODO documenter"""
|
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-12-31 22:18:13 +01:00
|
|
|
formsemestre: FormSemestre = FormSemestre.get_formsemestre(formsemestre_id)
|
|
|
|
|
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] = []
|
|
|
|
|
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)
|
2023-12-31 22:18:13 +01:00
|
|
|
groups_infos = sco_groups_view.DisplayedGroupsInfos(
|
|
|
|
group_ids, formsemestre_id=formsemestre.id, select_all_when_unspecified=True
|
|
|
|
)
|
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
|
|
|
|
]
|
|
|
|
)
|
2023-12-31 22:18:13 +01:00
|
|
|
# XXX utiliser des instances d'Identite et non des dict
|
|
|
|
# puis trier avec etud.sort_key
|
|
|
|
# afin de bien prendre en compte nom usuel etc
|
2023-06-01 17:32:50 +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-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
|
|
|
],
|
|
|
|
)
|
|
|
|
|
|
|
|
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,
|
2023-12-31 22:18:13 +01:00
|
|
|
sem=formsemestre.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-12-10 20:59:32 +01:00
|
|
|
@bp.route("/signale_evaluation_abs/<int:evaluation_id>/<int:etudid>")
|
2023-09-06 11:21:53 +02:00
|
|
|
@scodoc
|
2023-12-10 20:59:32 +01:00
|
|
|
@permission_required(Permission.AbsChange)
|
|
|
|
def signale_evaluation_abs(etudid: int = None, evaluation_id: int = None):
|
2023-09-06 11:21:53 +02:00
|
|
|
"""
|
|
|
|
Signale l'absence d'un étudiant à une évaluation
|
2023-12-10 20:59:32 +01:00
|
|
|
Si la durée de l'évaluation est inférieure à 1 jour
|
|
|
|
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-09-06 11:21:53 +02:00
|
|
|
"""
|
2023-12-01 13:46:33 +01:00
|
|
|
etud = Identite.get_etud(etudid)
|
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(
|
2023-12-05 21:04:38 +01:00
|
|
|
"assiduites.ajout_assiduite_etud",
|
2023-09-06 11:21:53 +02:00
|
|
|
etudid=etudid,
|
|
|
|
evaluation_id=evaluation.id,
|
2023-12-07 12:38:47 +01:00
|
|
|
date_deb=evaluation.date_debut.strftime("%Y-%m-%dT%H:%M:%S"),
|
2023-09-06 11:21:53 +02:00
|
|
|
date_fin=evaluation.date_fin.strftime("%Y-%m-%dT%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,
|
|
|
|
)
|
2023-12-10 20:59:32 +01:00
|
|
|
except ScoValueError as exc:
|
2023-10-27 16:05:40 +02:00
|
|
|
# En cas d'erreur
|
2023-12-10 20:59:32 +01:00
|
|
|
msg: str = exc.args[0]
|
2023-09-07 09:17:25 +02:00
|
|
|
if "Duplication" in msg:
|
2023-12-01 13:46:33 +01:00
|
|
|
msg = """Une autre saisie concerne déjà cette période.
|
|
|
|
En cliquant sur continuer vous serez redirigé vers la page de
|
2023-12-05 21:04:38 +01:00
|
|
|
saisie de l'assiduité de l'étudiant."""
|
2023-09-07 09:17:25 +02:00
|
|
|
dest: str = url_for(
|
2023-12-05 21:04:38 +01:00
|
|
|
"assiduites.ajout_assiduite_etud",
|
2023-09-07 09:17:25 +02:00
|
|
|
etudid=etudid,
|
|
|
|
evaluation_id=evaluation.id,
|
2023-12-05 21:04:38 +01:00
|
|
|
date_deb=evaluation.date_debut.strftime(
|
|
|
|
"%Y-%m-%dT%H:%M:%S"
|
2024-01-18 17:05:43 +01:00
|
|
|
),
|
2023-09-07 09:17:25 +02:00
|
|
|
date_fin=evaluation.date_fin.strftime("%Y-%m-%dT%H:%M:%S"),
|
|
|
|
moduleimpl_id=evaluation.moduleimpl.id,
|
|
|
|
saisie_eval="true",
|
|
|
|
scodoc_dept=g.scodoc_dept,
|
|
|
|
duplication="oui",
|
|
|
|
)
|
2023-12-10 20:59:32 +01:00
|
|
|
raise ScoValueError(msg, dest) from exc
|
2023-09-06 11:21:53 +02:00
|
|
|
|
|
|
|
db.session.add(assiduite_unique)
|
|
|
|
db.session.commit()
|
|
|
|
|
2023-12-10 20:59:32 +01:00
|
|
|
# on flash 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-03 14:57:22 +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-03 14:57:22 +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-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,
|
|
|
|
)
|
|
|
|
|
2024-01-08 10:49:34 +01:00
|
|
|
periode_defaut = sco_preferences.get_preference(
|
|
|
|
"periode_defaut",
|
|
|
|
formsemestre_id=formsemestre_id,
|
|
|
|
dept_id=g.scodoc_dept_id,
|
|
|
|
)
|
|
|
|
|
2023-06-12 17:54:30 +02:00
|
|
|
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,
|
2024-01-08 10:49:34 +01:00
|
|
|
periode_defaut=periode_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
|
|
|
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
|
|
|
|
]
|
|
|
|
)
|
|
|
|
+ "}"
|
|
|
|
)
|
2024-01-11 17:19:56 +01:00
|
|
|
|
|
|
|
|
|
|
|
# --- Gestion du calendrier ---
|
|
|
|
|
|
|
|
|
|
|
|
def generate_calendar(
|
|
|
|
etudiant: Identite,
|
|
|
|
annee: int = None,
|
2024-01-16 16:15:48 +01:00
|
|
|
) -> dict[str, list["Jour"]]:
|
2024-01-11 17:19:56 +01:00
|
|
|
# Si pas d'année alors on prend l'année scolaire en cours
|
|
|
|
if annee is None:
|
|
|
|
annee = scu.annee_scolaire()
|
|
|
|
|
|
|
|
# On prend du 01/09 au 31/08
|
|
|
|
date_debut: datetime.datetime = datetime.datetime(annee, 9, 1, 0, 0)
|
|
|
|
date_fin: datetime.datetime = datetime.datetime(annee + 1, 8, 31, 23, 59)
|
|
|
|
|
|
|
|
# Filtrage des assiduités et des justificatifs en fonction de la periode / année
|
|
|
|
etud_assiduites: Query = scass.filter_by_date(
|
|
|
|
etudiant.assiduites,
|
|
|
|
Assiduite,
|
|
|
|
date_deb=date_debut,
|
|
|
|
date_fin=date_fin,
|
|
|
|
)
|
|
|
|
etud_justificatifs: Query = scass.filter_by_date(
|
|
|
|
etudiant.justificatifs,
|
|
|
|
Justificatif,
|
|
|
|
date_deb=date_debut,
|
|
|
|
date_fin=date_fin,
|
|
|
|
)
|
|
|
|
|
|
|
|
# Récupération des jours de l'année et de leurs assiduités/justificatifs
|
2024-01-16 16:15:48 +01:00
|
|
|
annee_par_mois: dict[str, list[Jour]] = _organize_by_month(
|
2024-01-11 17:19:56 +01:00
|
|
|
_get_dates_between(
|
|
|
|
deb=date_debut.date(),
|
|
|
|
fin=date_fin.date(),
|
|
|
|
),
|
|
|
|
etud_assiduites,
|
|
|
|
etud_justificatifs,
|
|
|
|
)
|
|
|
|
|
|
|
|
return annee_par_mois
|
|
|
|
|
|
|
|
|
|
|
|
class Jour:
|
|
|
|
"""Jour
|
|
|
|
Jour du calendrier
|
|
|
|
get_nom : retourne le numéro et le nom du Jour (ex: M19 / Mer 19)
|
|
|
|
"""
|
|
|
|
|
|
|
|
def __init__(self, date: datetime.date, assiduites: Query, justificatifs: Query):
|
|
|
|
self.date = date
|
|
|
|
self.assiduites = assiduites
|
|
|
|
self.justificatifs = justificatifs
|
|
|
|
|
|
|
|
def get_nom(self, mode_demi: bool = True) -> str:
|
2024-01-16 16:15:48 +01:00
|
|
|
str_jour: str = scu.DAY_NAMES[self.date.weekday()].capitalize()
|
|
|
|
return f"{str_jour[0] if mode_demi or self.is_non_work() else str_jour[:3]+' '}{self.date.day}"
|
2024-01-11 17:19:56 +01:00
|
|
|
|
|
|
|
def get_date(self) -> str:
|
|
|
|
return self.date.strftime("%d/%m/%Y")
|
|
|
|
|
|
|
|
def get_class(self, show_pres: bool = False, show_reta: bool = False) -> str:
|
|
|
|
etat = ""
|
|
|
|
est_just = ""
|
|
|
|
|
|
|
|
if self.is_non_work():
|
|
|
|
return "color nonwork"
|
|
|
|
|
|
|
|
etat = self._get_color_assiduites_cascade(
|
|
|
|
self._get_etats_from_assiduites(self.assiduites),
|
|
|
|
show_pres=show_pres,
|
|
|
|
show_reta=show_reta,
|
|
|
|
)
|
|
|
|
|
|
|
|
est_just = self._get_color_justificatifs_cascade(
|
|
|
|
self._get_etats_from_justificatifs(self.justificatifs),
|
|
|
|
)
|
|
|
|
|
|
|
|
return f"color {etat} {est_just}"
|
|
|
|
|
|
|
|
def get_demi_class(
|
|
|
|
self, matin: bool, show_pres: bool = False, show_reta: bool = False
|
|
|
|
) -> str:
|
|
|
|
# Transformation d'une heure "HH:MM" en time(h,m)
|
|
|
|
STR_TIME = lambda x: datetime.time(*list(map(int, x.split(":"))))
|
|
|
|
|
|
|
|
heure_midi = STR_TIME(ScoDocSiteConfig.get("assi_lunch_time", "13:00"))
|
|
|
|
|
|
|
|
if matin:
|
|
|
|
heure_matin = STR_TIME(ScoDocSiteConfig.get("assi_morning_time", "08:00"))
|
|
|
|
matin = (
|
|
|
|
# date debut
|
|
|
|
scu.localize_datetime(
|
|
|
|
datetime.datetime.combine(self.date, heure_matin)
|
|
|
|
),
|
|
|
|
# date fin
|
|
|
|
scu.localize_datetime(datetime.datetime.combine(self.date, heure_midi)),
|
|
|
|
)
|
|
|
|
assiduites_matin = [
|
|
|
|
assi
|
|
|
|
for assi in self.assiduites
|
|
|
|
if scu.is_period_overlapping((assi.date_debut, assi.date_fin), matin)
|
|
|
|
]
|
|
|
|
justificatifs_matin = [
|
|
|
|
justi
|
|
|
|
for justi in self.justificatifs
|
|
|
|
if scu.is_period_overlapping((justi.date_debut, justi.date_fin), matin)
|
|
|
|
]
|
|
|
|
|
|
|
|
etat = self._get_color_assiduites_cascade(
|
|
|
|
self._get_etats_from_assiduites(assiduites_matin),
|
|
|
|
show_pres=show_pres,
|
|
|
|
show_reta=show_reta,
|
|
|
|
)
|
|
|
|
|
|
|
|
est_just = self._get_color_justificatifs_cascade(
|
|
|
|
self._get_etats_from_justificatifs(justificatifs_matin),
|
|
|
|
)
|
|
|
|
|
|
|
|
return f"color {etat} {est_just}"
|
|
|
|
|
|
|
|
heure_soir = STR_TIME(ScoDocSiteConfig.get("assi_afternoon_time", "17:00"))
|
|
|
|
|
|
|
|
# séparation en demi journées
|
|
|
|
aprem = (
|
|
|
|
# date debut
|
|
|
|
scu.localize_datetime(datetime.datetime.combine(self.date, heure_midi)),
|
|
|
|
# date fin
|
|
|
|
scu.localize_datetime(datetime.datetime.combine(self.date, heure_soir)),
|
|
|
|
)
|
|
|
|
|
|
|
|
assiduites_aprem = [
|
|
|
|
assi
|
|
|
|
for assi in self.assiduites
|
|
|
|
if scu.is_period_overlapping((assi.date_debut, assi.date_fin), aprem)
|
|
|
|
]
|
|
|
|
|
|
|
|
justificatifs_aprem = [
|
|
|
|
justi
|
|
|
|
for justi in self.justificatifs
|
|
|
|
if scu.is_period_overlapping((justi.date_debut, justi.date_fin), aprem)
|
|
|
|
]
|
|
|
|
|
|
|
|
etat = self._get_color_assiduites_cascade(
|
|
|
|
self._get_etats_from_assiduites(assiduites_aprem),
|
|
|
|
show_pres=show_pres,
|
|
|
|
show_reta=show_reta,
|
|
|
|
)
|
|
|
|
|
|
|
|
est_just = self._get_color_justificatifs_cascade(
|
|
|
|
self._get_etats_from_justificatifs(justificatifs_aprem),
|
|
|
|
)
|
|
|
|
|
|
|
|
return f"color {etat} {est_just}"
|
|
|
|
|
|
|
|
def has_assiduites(self) -> bool:
|
|
|
|
return self.assiduites.count() > 0
|
|
|
|
|
|
|
|
def generate_minitimeline(self) -> str:
|
|
|
|
# Récupérer le référenciel de la timeline
|
|
|
|
STR_TIME = lambda x: _time_to_timedelta(
|
|
|
|
datetime.time(*list(map(int, x.split(":"))))
|
|
|
|
)
|
|
|
|
|
|
|
|
heure_matin: datetime.timedelta = STR_TIME(
|
|
|
|
ScoDocSiteConfig.get("assi_morning_time", "08:00")
|
|
|
|
)
|
|
|
|
heure_midi: datetime.timedelta = STR_TIME(
|
|
|
|
ScoDocSiteConfig.get("assi_lun_time", "13:00")
|
|
|
|
)
|
|
|
|
heure_soir: datetime.timedelta = STR_TIME(
|
|
|
|
ScoDocSiteConfig.get("assi_afternoon_time", "17:00")
|
|
|
|
)
|
|
|
|
# longueur_timeline = heure_soir - heure_matin
|
|
|
|
longueur_timeline: datetime.timedelta = heure_soir - heure_matin
|
|
|
|
|
|
|
|
# chaque block d'assiduité est défini par:
|
|
|
|
# longueur = ( (fin-deb) / longueur_timeline ) * 100
|
|
|
|
# emplacement = ( (deb - heure_matin) / longueur_timeline ) * 100
|
|
|
|
|
|
|
|
assiduite_blocks: list[dict[str, float | str]] = []
|
|
|
|
|
|
|
|
for assi in self.assiduites:
|
|
|
|
deb: datetime.timedelta = _time_to_timedelta(
|
|
|
|
assi.date_debut.time()
|
|
|
|
if assi.date_debut.date() == self.date
|
|
|
|
else heure_matin
|
|
|
|
)
|
|
|
|
fin: datetime.timedelta = _time_to_timedelta(
|
|
|
|
assi.date_fin.time()
|
|
|
|
if assi.date_fin.date() == self.date
|
|
|
|
else heure_soir
|
|
|
|
)
|
|
|
|
|
|
|
|
longueur: float = ((fin - deb) / longueur_timeline) * 100
|
|
|
|
emplacement: float = ((deb - heure_matin) / longueur_timeline) * 100
|
|
|
|
etat: str = scu.EtatAssiduite(assi.etat).name.lower()
|
|
|
|
est_just: str = "est_just" if assi.est_just else ""
|
|
|
|
|
|
|
|
assiduite_blocks.append(
|
|
|
|
{
|
|
|
|
"longueur": longueur,
|
|
|
|
"emplacement": emplacement,
|
|
|
|
"etat": etat,
|
|
|
|
"est_just": est_just,
|
|
|
|
"bubble": _generate_assiduite_bubble(assi),
|
|
|
|
"id": assi.assiduite_id,
|
|
|
|
}
|
|
|
|
)
|
|
|
|
|
|
|
|
return render_template(
|
|
|
|
"assiduites/widgets/minitimeline_simple.j2",
|
|
|
|
assi_blocks=assiduite_blocks,
|
|
|
|
)
|
|
|
|
|
|
|
|
def is_non_work(self):
|
|
|
|
return self.date.weekday() in scu.NonWorkDays.get_all_non_work_days(
|
|
|
|
dept_id=g.scodoc_dept_id
|
|
|
|
)
|
|
|
|
|
|
|
|
def _get_etats_from_assiduites(self, assiduites: Query) -> list[scu.EtatAssiduite]:
|
|
|
|
return list(set([scu.EtatAssiduite(assi.etat) for assi in assiduites]))
|
|
|
|
|
|
|
|
def _get_etats_from_justificatifs(
|
|
|
|
self, justificatifs: Query
|
|
|
|
) -> list[scu.EtatJustificatif]:
|
|
|
|
return list(set([scu.EtatJustificatif(justi.etat) for justi in justificatifs]))
|
|
|
|
|
|
|
|
def _get_color_assiduites_cascade(
|
|
|
|
self,
|
|
|
|
etats: list[scu.EtatAssiduite],
|
|
|
|
show_pres: bool = False,
|
|
|
|
show_reta: bool = False,
|
|
|
|
) -> str:
|
|
|
|
if scu.EtatAssiduite.ABSENT in etats:
|
|
|
|
return "absent"
|
|
|
|
if scu.EtatAssiduite.RETARD in etats and show_reta:
|
|
|
|
return "retard"
|
|
|
|
if scu.EtatAssiduite.PRESENT in etats and show_pres:
|
|
|
|
return "present"
|
|
|
|
|
|
|
|
return "sans_etat"
|
|
|
|
|
|
|
|
def _get_color_justificatifs_cascade(
|
|
|
|
self,
|
|
|
|
etats: list[scu.EtatJustificatif],
|
|
|
|
) -> str:
|
|
|
|
if scu.EtatJustificatif.VALIDE in etats:
|
|
|
|
return "est_just"
|
|
|
|
if scu.EtatJustificatif.ATTENTE in etats:
|
|
|
|
return "attente"
|
|
|
|
if scu.EtatJustificatif.MODIFIE in etats:
|
|
|
|
return "modifie"
|
|
|
|
if scu.EtatJustificatif.NON_VALIDE in etats:
|
|
|
|
return "invalide"
|
|
|
|
|
|
|
|
return ""
|
|
|
|
|
|
|
|
|
|
|
|
def _get_dates_between(deb: datetime.date, fin: datetime.date) -> list[datetime.date]:
|
|
|
|
resultat = []
|
|
|
|
date_actuelle = deb
|
|
|
|
while date_actuelle <= fin:
|
|
|
|
resultat.append(date_actuelle)
|
|
|
|
date_actuelle += datetime.timedelta(days=1)
|
|
|
|
return resultat
|
|
|
|
|
|
|
|
|
2024-01-16 16:15:48 +01:00
|
|
|
def _organize_by_month(days, assiduites, justificatifs) -> dict[str, list[Jour]]:
|
2024-01-11 17:19:56 +01:00
|
|
|
"""
|
|
|
|
Organiser les dates par mois.
|
|
|
|
"""
|
|
|
|
organized = {}
|
|
|
|
for date in days:
|
2024-01-16 16:15:48 +01:00
|
|
|
# Récupérer le mois en français
|
|
|
|
month = scu.MONTH_NAMES_ABBREV[date.month - 1]
|
2024-01-11 17:19:56 +01:00
|
|
|
# Ajouter le jour à la liste correspondante au mois
|
|
|
|
if month not in organized:
|
|
|
|
organized[month] = []
|
|
|
|
|
|
|
|
date_assiduites: Query = scass.filter_by_date(
|
|
|
|
assiduites,
|
|
|
|
Assiduite,
|
|
|
|
date_deb=datetime.datetime.combine(date, datetime.time(0, 0)),
|
|
|
|
date_fin=datetime.datetime.combine(date, datetime.time(23, 59, 59)),
|
|
|
|
)
|
|
|
|
|
|
|
|
date_justificatifs: Query = scass.filter_by_date(
|
|
|
|
justificatifs,
|
|
|
|
Justificatif,
|
|
|
|
date_deb=datetime.datetime.combine(date, datetime.time(0, 0)),
|
|
|
|
date_fin=datetime.datetime.combine(date, datetime.time(23, 59, 59)),
|
|
|
|
)
|
|
|
|
# On génère un `Jour` composé d'une date, et des assiduités/justificatifs du jour
|
|
|
|
jour: Jour = Jour(date, date_assiduites, date_justificatifs)
|
|
|
|
|
|
|
|
organized[month].append(jour)
|
|
|
|
|
|
|
|
return organized
|
|
|
|
|
|
|
|
|
|
|
|
def _time_to_timedelta(t: datetime.time) -> datetime.timedelta:
|
|
|
|
if isinstance(t, datetime.timedelta):
|
|
|
|
return t
|
|
|
|
return datetime.timedelta(hours=t.hour, minutes=t.minute, seconds=t.second)
|
|
|
|
|
|
|
|
|
|
|
|
def _generate_assiduite_bubble(assiduite: Assiduite) -> str:
|
|
|
|
# Récupérer informations modules impl
|
|
|
|
moduleimpl_infos: str = assiduite.get_module(traduire=True)
|
|
|
|
|
|
|
|
# Récupérer informations saisie
|
|
|
|
saisie: str = assiduite.get_saisie()
|
|
|
|
|
|
|
|
return render_template(
|
|
|
|
"assiduites/widgets/assiduite_bubble.j2",
|
|
|
|
moduleimpl=moduleimpl_infos,
|
|
|
|
etat=scu.EtatAssiduite(assiduite.etat).name.lower(),
|
|
|
|
date_debut=assiduite.date_debut.strftime("%d/%m/%Y %H:%M"),
|
|
|
|
date_fin=assiduite.date_fin.strftime("%d/%m/%Y %H:%M"),
|
|
|
|
saisie=saisie,
|
|
|
|
)
|