Update opolka/ScoDoc from ScoDoc/ScoDoc #2

Merged
opolka merged 1272 commits from ScoDoc/ScoDoc:master into master 2024-05-27 09:11:04 +02:00
8 changed files with 189 additions and 97 deletions
Showing only changes of commit c620c3b0e1 - Show all commits

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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*")

View File

@ -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 [

View File

@ -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

View File

@ -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

View File

@ -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,