forked from ScoDoc/ScoDoc
Update opolka/ScoDoc from ScoDoc/ScoDoc #2
@ -126,6 +126,7 @@ class AjoutAssiOrJustForm(FlaskForm):
|
||||
|
||||
class AjoutAssiduiteEtudForm(AjoutAssiOrJustForm):
|
||||
"Formulaire de saisie d'une assiduité pour un étudiant"
|
||||
|
||||
description = TextAreaField(
|
||||
"Description",
|
||||
render_kw={
|
||||
@ -152,6 +153,7 @@ class AjoutAssiduiteEtudForm(AjoutAssiOrJustForm):
|
||||
|
||||
class AjoutJustificatifEtudForm(AjoutAssiOrJustForm):
|
||||
"Formulaire de saisie d'un justificatif pour un étudiant"
|
||||
|
||||
raison = TextAreaField(
|
||||
"Raison",
|
||||
render_kw={
|
||||
@ -176,6 +178,12 @@ class AjoutJustificatifEtudForm(AjoutAssiOrJustForm):
|
||||
|
||||
|
||||
class ChoixDateForm(FlaskForm):
|
||||
"""
|
||||
Formulaire de choix de date
|
||||
(utilisé par la page de choix de date
|
||||
si la date courante n'est pas dans le semestre)
|
||||
"""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
"Init form, adding a filed for our error messages"
|
||||
super().__init__(*args, **kwargs)
|
||||
|
@ -5,7 +5,6 @@ from datetime import datetime
|
||||
|
||||
from flask_login import current_user
|
||||
from flask_sqlalchemy.query import Query
|
||||
from sqlalchemy.exc import DataError
|
||||
|
||||
from app import db, log, g, set_sco_dept
|
||||
from app.models import (
|
||||
@ -89,6 +88,8 @@ class Assiduite(ScoDocModel):
|
||||
lazy="select",
|
||||
)
|
||||
|
||||
# Argument "restrict" obligatoire car on override la fonction "to_dict" de ScoDocModel
|
||||
# pylint: disable-next=unused-argument
|
||||
def to_dict(self, format_api=True, restrict: bool | None = None) -> dict:
|
||||
"""Retourne la représentation json de l'assiduité
|
||||
restrict n'est pas utilisé ici.
|
||||
@ -307,6 +308,9 @@ class Assiduite(ScoDocModel):
|
||||
|
||||
def supprime(self):
|
||||
"Supprime l'assiduité. Log et commit."
|
||||
|
||||
# Obligatoire car import circulaire sinon
|
||||
# pylint: disable-next=import-outside-toplevel
|
||||
from app.scodoc import sco_assiduites as scass
|
||||
|
||||
if g.scodoc_dept is None and self.etudiant.dept_id is not None:
|
||||
@ -356,7 +360,7 @@ class Assiduite(ScoDocModel):
|
||||
|
||||
date: str = self.entry_date.strftime("%d/%m/%Y à %H:%M")
|
||||
utilisateur: str = ""
|
||||
if self.user != None:
|
||||
if self.user is not None:
|
||||
self.user: User
|
||||
utilisateur = f"par {self.user.get_prenomnom()}"
|
||||
|
||||
@ -515,6 +519,8 @@ class Justificatif(ScoDocModel):
|
||||
def create_justificatif(
|
||||
cls,
|
||||
etudiant: Identite,
|
||||
# On a besoin des arguments mais on utilise "locals" pour les récupérer
|
||||
# pylint: disable=unused-argument
|
||||
date_debut: datetime,
|
||||
date_fin: datetime,
|
||||
etat: EtatJustificatif,
|
||||
@ -538,8 +544,10 @@ class Justificatif(ScoDocModel):
|
||||
|
||||
def supprime(self):
|
||||
"Supprime le justificatif. Log et commit."
|
||||
|
||||
# Obligatoire car import circulaire sinon
|
||||
# pylint: disable-next=import-outside-toplevel
|
||||
from app.scodoc import sco_assiduites as scass
|
||||
from app.scodoc.sco_archives_justificatifs import JustificatifArchiver
|
||||
|
||||
# Récupération de l'archive du justificatif
|
||||
archive_name: str = self.fichier
|
||||
|
@ -20,8 +20,11 @@ class Trace:
|
||||
|
||||
Role des fichiers traces :
|
||||
- Sauvegarder la date de dépôt du fichier
|
||||
- Sauvegarder la date de suppression du fichier (dans le cas de plusieurs fichiers pour un même justif)
|
||||
- Sauvegarder l'user_id de l'utilisateur ayant déposé le fichier (=> permet de montrer les fichiers qu'aux personnes qui l'on déposé / qui ont le rôle AssiJustifView)
|
||||
- Sauvegarder la date de suppression du fichier
|
||||
(dans le cas de plusieurs fichiers pour un même justif)
|
||||
- Sauvegarder l'user_id de l'utilisateur ayant déposé le fichier
|
||||
(=> permet de montrer les fichiers qu'aux personnes
|
||||
qui l'on déposé / qui ont le rôle AssiJustifView)
|
||||
|
||||
_trace.csv :
|
||||
nom_fichier_srv,datetime_depot,datetime_suppr,user_id
|
||||
|
@ -21,6 +21,7 @@ from app.models.assiduites import Assiduite, Justificatif, compute_assiduites_ju
|
||||
from app.scodoc import sco_formsemestre_inscriptions
|
||||
from app.scodoc import sco_preferences
|
||||
from app.scodoc import sco_cache
|
||||
from app.scodoc import sco_compute_moy
|
||||
from app.scodoc import sco_etud
|
||||
import app.scodoc.sco_utils as scu
|
||||
|
||||
@ -37,21 +38,34 @@ class CountCalculator:
|
||||
------------
|
||||
1. Initialisation : La classe peut être initialisée avec des horaires personnalisés
|
||||
pour le matin, le midi et le soir, ainsi qu'une durée de pause déjeuner.
|
||||
Si non spécifiés, les valeurs par défaut seront chargées depuis la configuration `ScoDocSiteConfig`.
|
||||
Si non spécifiés, les valeurs par défaut seront
|
||||
chargées depuis la configuration `ScoDocSiteConfig`.
|
||||
Exemple d'initialisation :
|
||||
calculator = CountCalculator(morning="08:00", noon="13:00", evening="18:00", nb_heures_par_jour=8)
|
||||
calculator = CountCalculator(
|
||||
morning="08:00",
|
||||
noon="13:00",
|
||||
evening="18:00",
|
||||
nb_heures_par_jour=8
|
||||
)
|
||||
|
||||
2. Ajout d'assiduités :
|
||||
Exemple d'ajout d'assiduité :
|
||||
- calculator.compute_assiduites(etudiant.assiduites)
|
||||
- calculator.compute_assiduites([<Assiduite>, <Assiduite>, <Assiduite>, <Assiduite>])
|
||||
- calculator.compute_assiduites([
|
||||
<Assiduite>,
|
||||
<Assiduite>,
|
||||
<Assiduite>,
|
||||
<Assiduite>
|
||||
])
|
||||
|
||||
3. Accès aux métriques : Après l'ajout des assiduités, on peut accéder aux métriques telles que :
|
||||
3. Accès aux métriques : Après l'ajout des assiduités,
|
||||
on peut accéder aux métriques telles que :
|
||||
le nombre total de jours, de demi-journées et d'heures calculées.
|
||||
Exemple d'accès aux métriques :
|
||||
metrics = calculator.to_dict()
|
||||
|
||||
4.Réinitialisation du comptage: Si besoin on peut réinitialisé le compteur sans perdre la configuration
|
||||
4.Réinitialisation du comptage: Si besoin on peut réinitialiser
|
||||
le compteur sans perdre la configuration
|
||||
(horaires personnalisés)
|
||||
Exemple de réinitialisation :
|
||||
calculator.reset()
|
||||
@ -61,8 +75,10 @@ class CountCalculator:
|
||||
- reset() : Réinitialise les compteurs de la classe.
|
||||
- add_half_day(day: date, is_morning: bool) : Ajoute une demi-journée au comptage.
|
||||
- add_day(day: date) : Ajoute un jour complet au comptage.
|
||||
- compute_long_assiduite(assi: Assiduite) : Traite les assiduités s'étendant sur plus d'un jour.
|
||||
- compute_assiduites(assiduites: Query | list) : Calcule les métriques pour une collection d'assiduités.
|
||||
- compute_long_assiduite(assi: Assiduite) : Traite les assiduités
|
||||
s'étendant sur plus d'un jour.
|
||||
- compute_assiduites(assiduites: Query | list) : Calcule les métriques pour
|
||||
une collection d'assiduités.
|
||||
- to_dict() : Retourne les métriques sous forme de dictionnaire.
|
||||
|
||||
Notes :
|
||||
@ -85,27 +101,20 @@ class CountCalculator:
|
||||
evening: str = None,
|
||||
nb_heures_par_jour: int = None,
|
||||
) -> None:
|
||||
# Transformation d'une heure "HH:MM" en time(h,m)
|
||||
STR_TIME = lambda x: time(*list(map(int, x.split(":"))))
|
||||
|
||||
self.morning: time = STR_TIME(
|
||||
self.morning: time = str_to_time(
|
||||
morning if morning else ScoDocSiteConfig.get("assi_morning_time", "08:00")
|
||||
)
|
||||
# Date pivot pour déterminer les demi-journées
|
||||
self.noon: time = STR_TIME(
|
||||
self.noon: time = str_to_time(
|
||||
noon if noon else ScoDocSiteConfig.get("assi_lunch_time", "13:00")
|
||||
)
|
||||
self.evening: time = STR_TIME(
|
||||
self.evening: time = str_to_time(
|
||||
evening if evening else ScoDocSiteConfig.get("assi_afternoon_time", "18:00")
|
||||
)
|
||||
|
||||
self.non_work_days: list[scu.NonWorkDays] = (
|
||||
scu.NonWorkDays.get_all_non_work_days(dept_id=g.scodoc_dept_id)
|
||||
)
|
||||
|
||||
delta_total: timedelta = datetime.combine(
|
||||
date.min, self.evening
|
||||
) - datetime.combine(date.min, self.morning)
|
||||
self.non_work_days: list[
|
||||
scu.NonWorkDays
|
||||
] = scu.NonWorkDays.get_all_non_work_days(dept_id=g.scodoc_dept_id)
|
||||
|
||||
# Sera utilisé pour les assiduités longues (> 1 journée)
|
||||
self.nb_heures_par_jour = (
|
||||
@ -340,17 +349,27 @@ class CountCalculator:
|
||||
|
||||
def setup_data(self):
|
||||
"""Met en forme les données
|
||||
pour les journées et les demi-journées : au lieu d'avoir list[str] on a le nombre (len(list[str]))
|
||||
pour les journées et les demi-journées :
|
||||
au lieu d'avoir list[str] on a le nombre (len(list[str]))
|
||||
"""
|
||||
for key in self.data:
|
||||
self.data[key]["journee"] = len(self.data[key]["journee"])
|
||||
self.data[key]["demi"] = len(self.data[key]["demi"])
|
||||
for value in self.data.values():
|
||||
value["journee"] = len(value["journee"])
|
||||
value["demi"] = len(value["demi"])
|
||||
|
||||
def to_dict(self, only_total: bool = True) -> dict[str, int | float]:
|
||||
"""Retourne les métriques sous la forme d'un dictionnaire"""
|
||||
return self.data["total"] if only_total else self.data
|
||||
|
||||
|
||||
def str_to_time(time_str: str) -> time:
|
||||
"""Convertit une chaîne de caractères représentant une heure en objet time
|
||||
exemples :
|
||||
- "08:00" -> time(8, 0)
|
||||
- "18:00:00" -> time(18, 0, 0)
|
||||
"""
|
||||
return time(*list(map(int, time_str.split(":"))))
|
||||
|
||||
|
||||
def get_assiduites_stats(
|
||||
assiduites: Query, metric: str = "all", filtered: dict[str, object] = None
|
||||
) -> dict[str, int | float]:
|
||||
@ -756,7 +775,6 @@ def invalidate_assiduites_etud_date(etudid: int, the_date: datetime):
|
||||
pour cet étudiant et cette date.
|
||||
Invalide cache absence et caches semestre
|
||||
"""
|
||||
from app.scodoc import sco_compute_moy
|
||||
|
||||
# Semestres a cette date:
|
||||
etud = sco_etud.get_etud_info(etudid=etudid, filled=True)
|
||||
@ -818,4 +836,4 @@ def simple_invalidate_cache(obj: dict, etudid: str | int = None):
|
||||
pattern=f"tableau-etud-{etudid}*"
|
||||
)
|
||||
# Invalide les tableaux "bilan dept"
|
||||
sco_cache.RequeteTableauAssiduiteCache.delete_pattern(pattern=f"tableau-dept*")
|
||||
sco_cache.RequeteTableauAssiduiteCache.delete_pattern(pattern="tableau-dept*")
|
||||
|
@ -130,7 +130,8 @@ def print_progress_bar(
|
||||
decimals - Optional : nombres de chiffres après la virgule (Int)
|
||||
length - Optional : taille de la barre en nombre de caractères (Int)
|
||||
fill - Optional : charactère de remplissange de la barre (Str)
|
||||
autosize - Optional : Choisir automatiquement la taille de la barre en fonction du terminal (Bool)
|
||||
autosize - Optional : Choisir automatiquement la taille de la barre
|
||||
en fonction du terminal (Bool)
|
||||
"""
|
||||
percent = ("{0:." + str(decimals) + "f}").format(100 * (iteration / float(total)))
|
||||
color = TerminalColor.RED
|
||||
@ -174,11 +175,15 @@ class BiDirectionalEnum(Enum):
|
||||
@classmethod
|
||||
def contains(cls, attr: str):
|
||||
"""Vérifie sur un attribut existe dans l'enum"""
|
||||
|
||||
# Existe dans la classe parent de Enum (EnumType)
|
||||
# pylint: disable-next=no-member
|
||||
return attr.upper() in cls._member_names_
|
||||
|
||||
@classmethod
|
||||
def all(cls, keys=True):
|
||||
"""Retourne toutes les clés de l'enum"""
|
||||
# pylint: disable-next=no-member
|
||||
return cls._member_names_ if keys else list(cls._value2member_map_.keys())
|
||||
|
||||
@classmethod
|
||||
@ -207,6 +212,9 @@ class EtatAssiduite(int, BiDirectionalEnum):
|
||||
ABSENT = 2
|
||||
|
||||
def version_lisible(self) -> str:
|
||||
"""Retourne une version lisible des états d'assiduités
|
||||
Est utilisé pour les vues.
|
||||
"""
|
||||
return {
|
||||
EtatAssiduite.PRESENT: "Présence",
|
||||
EtatAssiduite.ABSENT: "Absence",
|
||||
@ -225,6 +233,9 @@ class EtatJustificatif(int, BiDirectionalEnum):
|
||||
MODIFIE = 3
|
||||
|
||||
def version_lisible(self) -> str:
|
||||
"""Retourne une version lisible des états de justificatifs
|
||||
Est utilisé pour les vues.
|
||||
"""
|
||||
return {
|
||||
EtatJustificatif.VALIDE: "valide",
|
||||
EtatJustificatif.ATTENTE: "soumis",
|
||||
@ -254,11 +265,13 @@ class NonWorkDays(int, BiDirectionalEnum):
|
||||
cls, formsemestre_id: int = None, dept_id: int = None
|
||||
) -> list["NonWorkDays"]:
|
||||
"""
|
||||
get_all_non_work_days Récupère la liste des non workdays (str) depuis les préférences
|
||||
get_all_non_work_days Récupère la liste des non workdays
|
||||
(str) depuis les préférences
|
||||
puis renvoie une liste BiDirectionnalEnum<int> NonWorkDays
|
||||
|
||||
Example:
|
||||
non_work_days : list[NonWorkDays] = NonWorkDays.get_all_non_work_days(dept_id=g.scodoc_dept_id)
|
||||
non_work_days : list[NonWorkDays] =
|
||||
NonWorkDays.get_all_non_work_days(dept_id=g.scodoc_dept_id)
|
||||
if datetime.datetime.now().weekday() in non_work_days:
|
||||
print("Aujourd'hui est un jour non travaillé")
|
||||
|
||||
@ -269,6 +282,8 @@ class NonWorkDays(int, BiDirectionalEnum):
|
||||
Returns:
|
||||
list[NonWorkDays]: La liste des NonWorkDays en version BiDirectionnalEnum<int>
|
||||
"""
|
||||
# Import circulaire
|
||||
# pylint: disable=import-outside-toplevel
|
||||
from app.scodoc import sco_preferences
|
||||
|
||||
return [
|
||||
|
@ -1,9 +1,14 @@
|
||||
"""
|
||||
Gestion des listes d'assiduités et justificatifs
|
||||
(affichage, pagination, filtrage, options d'affichage, tableaux)
|
||||
"""
|
||||
|
||||
from datetime import datetime
|
||||
|
||||
from flask import url_for
|
||||
from flask_login import current_user
|
||||
from flask_sqlalchemy.query import Query
|
||||
from sqlalchemy import desc, literal, literal_column, union, asc
|
||||
from sqlalchemy import desc, literal, union, asc
|
||||
|
||||
from app import db, g
|
||||
from app.auth.models import User
|
||||
@ -34,9 +39,11 @@ class Pagination:
|
||||
On peut ensuite récupérer les éléments de la page courante avec la méthode `items()`
|
||||
|
||||
Cette classe ne permet pas de changer de page.
|
||||
(Pour cela, il faut créer une nouvelle instance, avec la collection originelle et la nouvelle page)
|
||||
(Pour cela, il faut créer une nouvelle instance,
|
||||
avec la collection originelle et la nouvelle page)
|
||||
|
||||
l'intéret est de ne pas garder en mémoire toute la collection, mais seulement la page courante
|
||||
l'intéret est de ne pas garder en mémoire toute la collection,
|
||||
mais seulement la page courante
|
||||
|
||||
"""
|
||||
|
||||
@ -45,9 +52,11 @@ class Pagination:
|
||||
__init__ Instancie un nouvel objet Pagination
|
||||
|
||||
Args:
|
||||
collection (list): La collection à paginer. Il s'agit par exemple d'une requête
|
||||
collection (list): La collection à paginer.
|
||||
Il s'agit par exemple d'une requête
|
||||
page (int, optional): le numéro de la page à voir. Defaults to 1.
|
||||
per_page (int, optional): le nombre d'éléments par page. Defaults to -1. (-1 = pas de pagination/tout afficher)
|
||||
per_page (int, optional): le nombre d'éléments par page.
|
||||
Defaults to -1. (-1 = pas de pagination/tout afficher)
|
||||
"""
|
||||
# par défaut le total des pages est 1 (même si la collection est vide)
|
||||
self.total_pages = 1
|
||||
@ -231,15 +240,17 @@ class ListeAssiJusti(tb.Table):
|
||||
attributs `page` et `NB_PAR_PAGE` de la classe `ListeAssiJusti`.
|
||||
|
||||
Args:
|
||||
collection (list): La collection à paginer. Il s'agit par exemple d'une requête qui a déjà
|
||||
collection (list): La collection à paginer.
|
||||
Il s'agit par exemple d'une requête qui a déjà
|
||||
été construite et qui est prête à être exécutée.
|
||||
|
||||
Returns:
|
||||
Pagination: Un objet Pagination qui encapsule les résultats de la requête paginée.
|
||||
Pagination: Un objet Pagination qui encapsule les résultats de
|
||||
la requête paginée.
|
||||
|
||||
Note:
|
||||
Cette méthode ne modifie pas la collection originelle; elle renvoie plutôt un nouvel
|
||||
objet qui contient les résultats paginés.
|
||||
Cette méthode ne modifie pas la collection originelle;
|
||||
elle renvoie plutôt un nouvel objet qui contient les résultats paginés.
|
||||
"""
|
||||
return Pagination(
|
||||
collection,
|
||||
@ -251,29 +262,35 @@ class ListeAssiJusti(tb.Table):
|
||||
"""
|
||||
Combine les requêtes d'assiduités et de justificatifs en une seule requête.
|
||||
|
||||
Cette fonction prend en entrée deux requêtes optionnelles, une pour les assiduités
|
||||
et une pour les justificatifs, et renvoie une requête combinée qui sélectionne
|
||||
un ensemble spécifique de colonnes pour chaque type d'objet.
|
||||
Cette fonction prend en entrée deux requêtes optionnelles,
|
||||
une pour les assiduités et une pour les justificatifs,
|
||||
et renvoie une requête combinée qui sélectionne un ensemble
|
||||
spécifique de colonnes pour chaque type d'objet.
|
||||
|
||||
Les colonnes sélectionnées sont:
|
||||
- obj_id: l'identifiant de l'objet (assiduite_id pour les assiduités, justif_id pour les justificatifs)
|
||||
- obj_id: l'identifiant de l'objet
|
||||
(assiduite_id pour les assiduités, justif_id pour les justificatifs)
|
||||
- etudid: l'identifiant de l'étudiant
|
||||
- entry_date: la date de saisie de l'objet
|
||||
- date_debut: la date de début de l'objet
|
||||
- date_fin: la date de fin de l'objet
|
||||
- etat: l'état de l'objet
|
||||
- type: le type de l'objet ("assiduite" pour les assiduités, "justificatif" pour les justificatifs)
|
||||
- type: le type de l'objet
|
||||
("assiduite" pour les assiduités, "justificatif" pour les justificatifs)
|
||||
- est_just : si l'assiduité est justifié (booléen) None pour les justificatifs
|
||||
- user_id : l'identifiant de l'utilisateur qui a signalé l'assiduité ou le justificatif
|
||||
- user_id : l'identifiant de l'utilisateur qui a
|
||||
signalé l'assiduité ou le justificatif
|
||||
|
||||
Args:
|
||||
query_assiduite (sqlalchemy.orm.Query, optional): Une requête SQLAlchemy
|
||||
pour les assiduités.
|
||||
Si None (default), aucune assiduité ne sera incluse dans la requête combinée.
|
||||
Si None (default), aucune assiduité ne sera incluse
|
||||
dans la requête combinée.
|
||||
|
||||
query_justificatif (sqlalchemy.orm.Query, optional): Une requête SQLAlchemy
|
||||
pour les justificatifs.
|
||||
Si None (default), aucun justificatif ne sera inclus dans la requête combinée.
|
||||
Si None (default), aucun justificatif ne sera
|
||||
inclus dans la requête combinée.
|
||||
|
||||
Returns:
|
||||
sqlalchemy.orm.Query: Une requête combinée qui peut être exécutée pour
|
||||
@ -618,10 +635,15 @@ class AssiFiltre:
|
||||
|
||||
Args:
|
||||
type_obj (int, optional): type d'objet (0:Tout, 1: Assi, 2:Justi). Defaults to 0.
|
||||
entry_date (tuple[int, datetime], optional): (0: egal, 1: avant, 2: après) + datetime(avec TZ). Defaults to None.
|
||||
date_debut (tuple[int, datetime], optional): (0: egal, 1: avant, 2: après) + datetime(avec TZ). Defaults to None.
|
||||
date_fin (tuple[int, datetime], optional): (0: egal, 1: avant, 2: après) + datetime(avec TZ). Defaults to None.
|
||||
etats (list[int | EtatJustificatif | EtatAssiduite], optional): liste d'états valides (int | EtatJustificatif | EtatAssiduite). Defaults to None.
|
||||
entry_date (tuple[int, datetime], optional):
|
||||
(0: egal, 1: avant, 2: après) + datetime(avec TZ). Defaults to None.
|
||||
date_debut (tuple[int, datetime], optional):
|
||||
(0: egal, 1: avant, 2: après) + datetime(avec TZ). Defaults to None.
|
||||
date_fin (tuple[int, datetime], optional):
|
||||
(0: egal, 1: avant, 2: après) + datetime(avec TZ). Defaults to None.
|
||||
etats (list[int | EtatJustificatif | EtatAssiduite], optional):
|
||||
liste d'états valides (int | EtatJustificatif | EtatAssiduite).
|
||||
Defaults to None.
|
||||
"""
|
||||
|
||||
self.filtres = {"type_obj": type_obj}
|
||||
@ -753,6 +775,10 @@ class AssiJustifData:
|
||||
|
||||
@staticmethod
|
||||
def from_etudiants(*etudiants: Identite) -> "AssiJustifData":
|
||||
"""
|
||||
Génère un object AssiJustifData à partir d'une liste d'étudiants
|
||||
(Récupère les assiduités et justificatifs des étudiants)
|
||||
"""
|
||||
data = AssiJustifData()
|
||||
data.assiduites_query = Assiduite.query.filter(
|
||||
Assiduite.etudid.in_([e.etudid for e in etudiants])
|
||||
@ -764,4 +790,5 @@ class AssiJustifData:
|
||||
return data
|
||||
|
||||
def get(self) -> tuple[Query, Query]:
|
||||
"Renvoi les requêtes d'assiduités et justificatifs"
|
||||
return self.assiduites_query, self.justificatifs_query
|
||||
|
@ -37,7 +37,7 @@ class TableAssi(tb.Table):
|
||||
convert_values=False,
|
||||
**kwargs,
|
||||
):
|
||||
self.rows: list["RowEtud"] = [] # juste pour que VSCode nous aide sur .rows
|
||||
self.rows: list["RowAssi"] = [] # juste pour que VSCode nous aide sur .rows
|
||||
classes = ["gt_table"]
|
||||
self.dates = [str(dates[0]) + "T00:00", str(dates[1]) + "T23:59"]
|
||||
self.formsemestre = formsemestre
|
||||
|
@ -27,7 +27,6 @@
|
||||
import datetime
|
||||
import json
|
||||
import re
|
||||
from typing import Any
|
||||
|
||||
from flask import g, request, render_template, flash
|
||||
from flask import abort, url_for, redirect, Response
|
||||
@ -122,7 +121,6 @@ def bilan_dept():
|
||||
if formsemestre_id:
|
||||
try:
|
||||
formsemestre: FormSemestre = FormSemestre.get_formsemestre(formsemestre_id)
|
||||
annee = formsemestre.annee_scolaire()
|
||||
except AttributeError:
|
||||
formsemestre_id = ""
|
||||
|
||||
@ -533,7 +531,7 @@ def bilan_etud():
|
||||
# Récupération des assiduités et justificatifs de l'étudiant
|
||||
data = liste_assi.AssiJustifData(
|
||||
etud.assiduites.filter(
|
||||
Assiduite.etat != scu.EtatAssiduite.PRESENT, Assiduite.est_just == False
|
||||
Assiduite.etat != scu.EtatAssiduite.PRESENT, Assiduite.est_just is False
|
||||
),
|
||||
etud.justificatifs.filter(
|
||||
Justificatif.etat.in_(
|
||||
@ -860,6 +858,15 @@ def calendrier_assi_etud():
|
||||
@scodoc
|
||||
@permission_required(Permission.AbsChange)
|
||||
def choix_date() -> str:
|
||||
"""
|
||||
choix_date Choix de la date pour la saisie des assiduités
|
||||
|
||||
Route utilisée uniquement si la date courante n'est pas dans le semestre
|
||||
concerné par la requête vers une des pages suivantes :
|
||||
- saisie_assiduites_group
|
||||
- visu_assiduites_group
|
||||
|
||||
"""
|
||||
formsemestre_id = request.args.get("formsemestre_id")
|
||||
formsemestre: FormSemestre = FormSemestre.query.get_or_404(formsemestre_id)
|
||||
|
||||
@ -976,9 +983,6 @@ def signal_assiduites_group():
|
||||
if formsemestre.dept_id != g.scodoc_dept_id:
|
||||
abort(404, "groupes inexistants dans ce département")
|
||||
|
||||
# Vérification du forçage du module
|
||||
require_module = sco_preferences.get_preference("forcer_module", formsemestre_id)
|
||||
|
||||
# Récupération des étudiants des groupes
|
||||
etuds = [
|
||||
sco_etud.get_etud_info(etudid=m["etudid"], filled=True)[0]
|
||||
@ -1110,9 +1114,6 @@ def visu_assiduites_group():
|
||||
if formsemestre.dept_id != g.scodoc_dept_id:
|
||||
abort(404, "groupes inexistants dans ce département")
|
||||
|
||||
# Vérfication du forçage du module
|
||||
require_module = sco_preferences.get_preference("forcer_module", formsemestre_id)
|
||||
|
||||
# Récupération des étudiants du/des groupe(s)
|
||||
etuds = [
|
||||
sco_etud.get_etud_info(etudid=m["etudid"], filled=True)[0]
|
||||
@ -1784,22 +1785,6 @@ def signal_assiduites_diff():
|
||||
)
|
||||
etudiants = list(sorted(etudiants, key=lambda etud: etud.sort_key))
|
||||
|
||||
# Génération de l'HTML
|
||||
|
||||
header: str = html_sco_header.sco_header(
|
||||
page_title="Assiduité: saisie différée",
|
||||
init_qtip=True,
|
||||
cssstyles=[
|
||||
"css/assiduites.css",
|
||||
],
|
||||
javascripts=html_sco_header.BOOTSTRAP_MULTISELECT_JS
|
||||
+ [
|
||||
"js/assiduites.js",
|
||||
"js/date_utils.js",
|
||||
"js/etud_info.js",
|
||||
],
|
||||
)
|
||||
|
||||
if groups_infos.tous_les_etuds_du_sem:
|
||||
gr_tit = "en"
|
||||
else:
|
||||
@ -2078,8 +2063,8 @@ def _differee(
|
||||
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.
|
||||
periode (dict[str, str], optional):La période par défaut de la première colonne.
|
||||
formsemestre_id (int, optional): l'id du semestre pour le selecteur de module.
|
||||
|
||||
Returns:
|
||||
str: le widget (html/css/js)
|
||||
@ -2265,6 +2250,9 @@ def generate_calendar(
|
||||
etudiant: Identite,
|
||||
annee: int = None,
|
||||
) -> dict[str, list["Jour"]]:
|
||||
"""
|
||||
Génère le calendrier d'assiduité de l'étudiant pour une année scolaire donnée
|
||||
"""
|
||||
# Si pas d'année alors on prend l'année scolaire en cours
|
||||
if annee is None:
|
||||
annee = scu.annee_scolaire()
|
||||
@ -2312,13 +2300,26 @@ class Jour:
|
||||
self.justificatifs = justificatifs
|
||||
|
||||
def get_nom(self, mode_demi: bool = True) -> str:
|
||||
"""
|
||||
Renvoie le nom du jour
|
||||
"M19" ou "Mer 19"
|
||||
"""
|
||||
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}"
|
||||
return (
|
||||
f"{str_jour[0] if mode_demi or self.is_non_work() else str_jour[:3]+' '}"
|
||||
+ f"{self.date.day}"
|
||||
)
|
||||
|
||||
def get_date(self) -> str:
|
||||
"""
|
||||
Renvoie la date du jour au format "dd/mm/yyyy"
|
||||
"""
|
||||
return self.date.strftime("%d/%m/%Y")
|
||||
|
||||
def get_class(self, show_pres: bool = False, show_reta: bool = False) -> str:
|
||||
"""
|
||||
Retourne la classe css du jour (mode normal)
|
||||
"""
|
||||
etat = ""
|
||||
est_just = ""
|
||||
|
||||
@ -2340,13 +2341,16 @@ class Jour:
|
||||
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)
|
||||
str2time = lambda x: datetime.time(*list(map(int, x.split(":"))))
|
||||
"""
|
||||
Renvoie la class css de la demi journée
|
||||
"""
|
||||
|
||||
heure_midi = str2time(ScoDocSiteConfig.get("assi_lunch_time", "13:00"))
|
||||
heure_midi = scass.str_to_time(ScoDocSiteConfig.get("assi_lunch_time", "13:00"))
|
||||
|
||||
if matin:
|
||||
heure_matin = str2time(ScoDocSiteConfig.get("assi_morning_time", "08:00"))
|
||||
heure_matin = scass.str_to_time(
|
||||
ScoDocSiteConfig.get("assi_morning_time", "08:00")
|
||||
)
|
||||
matin = (
|
||||
# date debut
|
||||
scu.localize_datetime(
|
||||
@ -2382,7 +2386,9 @@ class Jour:
|
||||
|
||||
return f"color {etat} {est_just}"
|
||||
|
||||
heure_soir = str2time(ScoDocSiteConfig.get("assi_afternoon_time", "17:00"))
|
||||
heure_soir = scass.str_to_time(
|
||||
ScoDocSiteConfig.get("assi_afternoon_time", "17:00")
|
||||
)
|
||||
|
||||
# séparation en demi journées
|
||||
aprem = (
|
||||
@ -2421,21 +2427,24 @@ class Jour:
|
||||
return f"color {etat} {est_just}"
|
||||
|
||||
def has_assiduites(self) -> bool:
|
||||
"""
|
||||
Renverra True si le jour a des assiduités
|
||||
"""
|
||||
return self.assiduites.count() > 0
|
||||
|
||||
def generate_minitimeline(self) -> str:
|
||||
"""
|
||||
Génère la minitimeline du jour
|
||||
"""
|
||||
# Récupérer le référenciel de la timeline
|
||||
str2time = lambda x: _time_to_timedelta(
|
||||
scass.str_to_time = lambda x: _time_to_timedelta(
|
||||
datetime.time(*list(map(int, x.split(":"))))
|
||||
)
|
||||
|
||||
heure_matin: datetime.timedelta = str2time(
|
||||
heure_matin: datetime.timedelta = scass.str_to_time(
|
||||
ScoDocSiteConfig.get("assi_morning_time", "08:00")
|
||||
)
|
||||
heure_midi: datetime.timedelta = str2time(
|
||||
ScoDocSiteConfig.get("assi_lun_time", "13:00")
|
||||
)
|
||||
heure_soir: datetime.timedelta = str2time(
|
||||
heure_soir: datetime.timedelta = scass.str_to_time(
|
||||
ScoDocSiteConfig.get("assi_afternoon_time", "17:00")
|
||||
)
|
||||
# longueur_timeline = heure_soir - heure_matin
|
||||
@ -2484,17 +2493,21 @@ class Jour:
|
||||
)
|
||||
|
||||
def is_non_work(self):
|
||||
"""
|
||||
Renvoie True si le jour est un jour non travaillé
|
||||
(en fonction de la préférence du département)
|
||||
"""
|
||||
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]))
|
||||
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]))
|
||||
return list(set(scu.EtatJustificatif(justi.etat) for justi in justificatifs))
|
||||
|
||||
def _get_color_assiduites_cascade(
|
||||
self,
|
||||
|
Loading…
Reference in New Issue
Block a user