From 87e98b5478eece22bd267ec1c7570d462bc92f33 Mon Sep 17 00:00:00 2001 From: Iziram Date: Fri, 19 Jan 2024 17:06:01 +0100 Subject: [PATCH] Assiduites : WIP todos 2 --- app/api/justificatifs.py | 4 +- app/scodoc/sco_abs_notification.py | 1 - app/scodoc/sco_assiduites.py | 4 +- app/scodoc/sco_cache.py | 2 +- app/scodoc/sco_formsemestre_status.py | 2 +- app/scodoc/sco_preferences.py | 26 +-- app/scodoc/sco_utils.py | 6 +- app/static/js/assiduites.js | 1 + app/tables/liste_assiduites.py | 17 +- app/templates/assiduites/pages/bilan_dept.j2 | 208 ++---------------- app/templates/assiduites/pages/bilan_etud.j2 | 85 +------ .../pages/signal_assiduites_diff.j2 | 9 + .../assiduites/widgets/assiduite_bubble.j2 | 1 + app/templates/assiduites/widgets/conflict.j2 | 40 ++-- .../assiduites/widgets/minitimeline.j2 | 5 + .../widgets/moduleimpl_selector_multiple.j2 | 20 ++ app/views/assiduites.py | 205 ++++++++++------- tests/unit/test_assiduites.py | 77 +++++-- 18 files changed, 306 insertions(+), 407 deletions(-) create mode 100644 app/templates/assiduites/widgets/moduleimpl_selector_multiple.j2 diff --git a/app/api/justificatifs.py b/app/api/justificatifs.py index 611785ef..ff1487a9 100644 --- a/app/api/justificatifs.py +++ b/app/api/justificatifs.py @@ -151,7 +151,9 @@ def justificatifs(etudid: int = None, nip=None, ine=None, with_query: bool = Fal @as_json @permission_required(Permission.ScoView) def justificatifs_dept(dept_id: int = None, with_query: bool = False): - """XXX TODO missing doc""" + """ + Renvoie tous les justificatifs d'un département (en ajoutant un champs "formsemestre" si possible) + """ # Récupération du département et des étudiants du département dept: Departement = Departement.query.get(dept_id) diff --git a/app/scodoc/sco_abs_notification.py b/app/scodoc/sco_abs_notification.py index b97f4418..82caff29 100644 --- a/app/scodoc/sco_abs_notification.py +++ b/app/scodoc/sco_abs_notification.py @@ -108,7 +108,6 @@ def do_abs_notify( return # abort # Vérification fréquence (pour ne pas envoyer de mails trop souvent) - # TODO Mettre la fréquence dans les préférences assiduités abs_notify_max_freq = sco_preferences.get_preference("abs_notify_max_freq") destinations_filtered = [] for email_addr in destinations: diff --git a/app/scodoc/sco_assiduites.py b/app/scodoc/sco_assiduites.py index 1421ff28..6b65b279 100644 --- a/app/scodoc/sco_assiduites.py +++ b/app/scodoc/sco_assiduites.py @@ -763,5 +763,7 @@ def simple_invalidate_cache(obj: dict, etudid: str | int = None): # Invalide les caches des tableaux de l'étudiant sco_cache.RequeteTableauAssiduiteCache.delete_pattern( - pattern=f"tableau-etud-{etudid}:*" + pattern=f"tableau-etud-{etudid}*" ) + # Invalide les tableaux "bilan dept" + sco_cache.RequeteTableauAssiduiteCache.delete_pattern(pattern=f"tableau-dept*") diff --git a/app/scodoc/sco_cache.py b/app/scodoc/sco_cache.py index e6d3fa81..e72ee1bd 100644 --- a/app/scodoc/sco_cache.py +++ b/app/scodoc/sco_cache.py @@ -400,7 +400,7 @@ class ValidationsSemestreCache(ScoDocCache): class RequeteTableauAssiduiteCache(ScoDocCache): """ - clé : ":::>::" + clé : "::::::" Valeur = liste de dicts """ diff --git a/app/scodoc/sco_formsemestre_status.py b/app/scodoc/sco_formsemestre_status.py index 2b89c1f7..365a44c5 100755 --- a/app/scodoc/sco_formsemestre_status.py +++ b/app/scodoc/sco_formsemestre_status.py @@ -917,7 +917,7 @@ def _make_listes_sem(formsemestre: FormSemestre) -> str: diff --git a/app/scodoc/sco_preferences.py b/app/scodoc/sco_preferences.py index ede0698a..8aa38e25 100644 --- a/app/scodoc/sco_preferences.py +++ b/app/scodoc/sco_preferences.py @@ -541,18 +541,6 @@ class BasePreferences: "category": "abs", }, ), - ( - "abs_notify_max_freq", - { - "initvalue": 7, - "title": "Fréquence maximale de notification", - "explanation": "nb de jours minimum entre deux mails envoyés au même destinataire à propos d'un même étudiant ", - "size": 4, - "type": "int", - "convert_numbers": True, - "category": "abs", - }, - ), ( "abs_notify_abs_threshold", { @@ -710,11 +698,23 @@ class BasePreferences: "size": 10, "title": "Seuil d'alerte des absences", "type": "int", - "explanation": "Nombres d'absences limite avant alerte dans le bilan (utilisation de l'unité métrique ↑ )", + "explanation": "Nombres d'absences limite avant alerte (utilisation de l'unité métrique ↑ )", "category": "assi", "only_global": True, }, ), + ( + "abs_notify_max_freq", + { + "initvalue": 7, + "title": "Fréquence maximale de notification", + "explanation": "nb de jours minimum entre deux mails envoyés au même destinataire à propos d'un même étudiant ", + "size": 4, + "type": "int", + "convert_numbers": True, + "category": "abs", + }, + ), # portal ( "portal_url", diff --git a/app/scodoc/sco_utils.py b/app/scodoc/sco_utils.py index a6d5d8b9..9c9ba39e 100644 --- a/app/scodoc/sco_utils.py +++ b/app/scodoc/sco_utils.py @@ -298,15 +298,11 @@ def is_iso_formated(date: str, convert=False) -> bool or datetime.datetime or No return None if convert else False -def localize_datetime(date: datetime.datetime or str) -> datetime.datetime: +def localize_datetime(date: datetime.datetime) -> datetime.datetime: """Transforme une date sans offset en une date avec offset Tente de mettre l'offset de la timezone du serveur (ex : UTC+1) Si erreur, mettra l'offset UTC - - TODO : vérifier puis supprimer l'auto conversion str-> datetime """ - if isinstance(date, str): - date = is_iso_formated(date, convert=True) new_date: datetime.datetime = date if new_date.tzinfo is None: diff --git a/app/static/js/assiduites.js b/app/static/js/assiduites.js index bd22ecb2..85355a1e 100644 --- a/app/static/js/assiduites.js +++ b/app/static/js/assiduites.js @@ -1,3 +1,4 @@ +// TODO : Supprimer les fonctions non utilisées + optimiser les fonctions utilisées // <=== CONSTANTS and GLOBALS ===> let url; diff --git a/app/tables/liste_assiduites.py b/app/tables/liste_assiduites.py index f288faed..24a449b5 100644 --- a/app/tables/liste_assiduites.py +++ b/app/tables/liste_assiduites.py @@ -140,6 +140,7 @@ class ListeAssiJusti(tb.Table): type_obj, self.options.show_pres, self.options.show_reta, + self.options.show_desc, self.options.order[0], self.options.order[1], ], @@ -152,12 +153,18 @@ class ListeAssiJusti(tb.Table): assiduites_query_etudiants = self.table_data.assiduites_query # Non affichage des présences - if not self.options.show_pres: + if ( + not self.options.show_pres + and assiduites_query_etudiants is not None + ): assiduites_query_etudiants = assiduites_query_etudiants.filter( Assiduite.etat != EtatAssiduite.PRESENT ) # Non affichage des retards - if not self.options.show_reta: + if ( + not self.options.show_reta + and assiduites_query_etudiants is not None + ): assiduites_query_etudiants = assiduites_query_etudiants.filter( Assiduite.etat != EtatAssiduite.RETARD ) @@ -266,7 +273,7 @@ class ListeAssiJusti(tb.Table): ] if self.options.show_desc: - assiduites_entities.append(Assiduite.description.label("description")) + assiduites_entities.append(Assiduite.description.label("desc")) query_assiduite = query_assiduite.with_entities(*assiduites_entities) queries.append(query_assiduite) @@ -288,7 +295,7 @@ class ListeAssiJusti(tb.Table): ] if self.options.show_desc: - justificatifs_entities.append(Justificatif.raison.label("description")) + justificatifs_entities.append(Justificatif.raison.label("desc")) query_justificatif = query_justificatif.with_entities( *justificatifs_entities @@ -466,7 +473,7 @@ class RowAssiJusti(tb.Row): self.add_cell( "description", "Description", - self.ligne["description"] if self.ligne["description"] else "", + self.ligne["desc"] if self.ligne["desc"] else "", ) if self.table.options.show_module: if self.ligne["type"] == "assiduite": diff --git a/app/templates/assiduites/pages/bilan_dept.j2 b/app/templates/assiduites/pages/bilan_dept.j2 index c3815493..f4a20efb 100644 --- a/app/templates/assiduites/pages/bilan_dept.j2 +++ b/app/templates/assiduites/pages/bilan_dept.j2 @@ -1,187 +1,27 @@ -{% include "assiduites/widgets/tableau_base.j2" %} - +{% extends "sco_page.j2" %} +{% block styles %} + {{super()}} + +{% endblock styles %} +{% block scripts %} +{{ super() }} + +{% endblock scripts %} +{% block app_content %} +

Traitement de l'assiduité

+

+Pour saisir l'assiduité ou consulter les états, il est recommandé de passer par +le semestre concerné (saisie par jour ou saisie différée). +

+

Pour signaler, annuler ou justifier l'assiduité d'un seul étudiant, + choisissez d'abord la personne concernée :

+
+{{search_etud | safe}} +
+{{billets | safe}} +
- -

Justificatifs en attente (ou modifiés)

- - - - - {% include "assiduites/widgets/tableau_justi.j2" %} + {{tableau | safe }}
- -
- Année scolaire 2022-2023 Changer année: - -
- -
-

Gestion des justificatifs

-

- Faites - clic droit sur une ligne du tableau pour afficher le menu contextuel : -

    -
  • Détails : Affiche les détails du justificatif sélectionné
  • -
  • Editer : Permet de modifier le justificatif (dates, etat, ajouter/supprimer fichier etc)
  • -
  • Supprimer : Permet de supprimer le justificatif (Action Irréversible)
  • -
-

-
- - \ No newline at end of file +{% endblock app_content %} \ No newline at end of file diff --git a/app/templates/assiduites/pages/bilan_etud.j2 b/app/templates/assiduites/pages/bilan_etud.j2 index 7c1f1c7e..6f9a320f 100644 --- a/app/templates/assiduites/pages/bilan_etud.j2 +++ b/app/templates/assiduites/pages/bilan_etud.j2 @@ -25,27 +25,7 @@
- -

Absences et retards non justifiés

- - {# TODO Utiliser python tableau plutot que js tableau #} -
Attention, cette page utilise des couleurs et conventions différentes - de celles des autres pages ScoDoc: elle sera prochainement modifée, merci de votre patience. -
- - - - - - {% include "assiduites/widgets/tableau_assi.j2" %} - -

Justificatifs en attente (ou modifiés)

- - - - - {% include "assiduites/widgets/tableau_justi.j2" %} - + {{tableau | safe }}
@@ -60,29 +40,6 @@ département)

Les statistiques sont calculées entre les deux dates sélectionnées. Après modification des dates, appuyer sur le bouton "Actualiser"

-

Gestion des justificatifs

-

- Faites - clic droit sur une ligne du tableau pour afficher le menu - contextuel : -

-
    -
  • Détails : affiche les détails du justificatif sélectionné
  • -
  • Éditer : modifie le justificatif (dates, état, ajouter/supprimer fichier, etc.)
  • -
  • Supprimer : supprime le justificatif (action irréversible)
  • -
- -

Gestion de l'assiduité

-

- Faites - clic droit sur une ligne du tableau pour afficher le menu - contextuel : -

-
    -
  • Détails : affiche les détails de l'élément sélectionnée
  • -
  • Editer : modifie l'élément (module, état)
  • -
  • Supprimer : supprime l'élément (action irréversible)
  • -
@@ -275,48 +232,8 @@ window.addEventListener('load', () => { - filterAssiduites = { - "columns": [ - "entry_date", - "date_debut", - "date_fin", - "etat", - "moduleimpl_id", - "est_just" - ], - "filters": { - "etat": [ - "retard", - "absent" - ], - "moduleimpl_id": "", - "est_just": "false" - } - }; - - filterJustificatifs = { - "columns": [ - "entry_date", - "date_debut", - "date_fin", - "etat", - "raison", - "fichier" - ], - "filters": { - "etat": [ - "attente", - "modifie" - ] - } - } - document.getElementById('stats_date_fin').value = assi_date_fin; document.getElementById('stats_date_debut').value = assi_date_debut; - - - - loadAll(); stats(); }) diff --git a/app/templates/assiduites/pages/signal_assiduites_diff.j2 b/app/templates/assiduites/pages/signal_assiduites_diff.j2 index ed801d75..d6066a4b 100644 --- a/app/templates/assiduites/pages/signal_assiduites_diff.j2 +++ b/app/templates/assiduites/pages/signal_assiduites_diff.j2 @@ -1,5 +1,14 @@ +{# + + - TODO : revoir le fonctionnement de cette page (trop lente / complexe) + - Utiliser majoritairement du python + #}

Signalement différé de l'assiduité {{gr |safe}}

+
Attention, cette page utilise des couleurs et conventions différentes + de celles des autres pages ScoDoc: elle sera prochainement modifée, merci de votre patience. +
+

{{sem | safe }}

{{diff | safe}} diff --git a/app/templates/assiduites/widgets/assiduite_bubble.j2 b/app/templates/assiduites/widgets/assiduite_bubble.j2 index 3db6ea25..23004a86 100644 --- a/app/templates/assiduites/widgets/assiduite_bubble.j2 +++ b/app/templates/assiduites/widgets/assiduite_bubble.j2 @@ -3,5 +3,6 @@
{{date_debut}}
{{date_fin}}
État: {{etat}}
+
Motif: {{motif}}
{{saisie}}
\ No newline at end of file diff --git a/app/templates/assiduites/widgets/conflict.j2 b/app/templates/assiduites/widgets/conflict.j2 index d7c8638e..90883589 100644 --- a/app/templates/assiduites/widgets/conflict.j2 +++ b/app/templates/assiduites/widgets/conflict.j2 @@ -77,12 +77,6 @@ const duration = (endTime - startTime) / 1000 / 60; const percent = (duration / (t_end * 60 - t_start * 60)) * 100 - - if (percent > 100) { - console.log(start, end); - console.log(startTime, endTime) - } - return percent + "%"; } @@ -253,13 +247,11 @@ */ splitAssiduiteModal() { //Préparation du prompt - // TODO utiliser timepicker jquery + utiliser les bornes (t_start et t_end) - const htmlPrompt = `Entrez l'heure de séparation (HH:mm) : - `; + const htmlPrompt = `Entrez l'heure de séparation + `; const fieldSet = document.createElement("fieldset"); - fieldSet.classList.add("fieldsplit"); + fieldSet.classList.add("fieldsplit", "timepicker"); fieldSet.innerHTML = htmlPrompt; //Callback de division @@ -317,11 +309,28 @@ "L'heure de séparation doit être compris dans la période de l'assiduité sélectionnée." ); - openAlertModal("Attention", att, "", "var(--color-warning))"); + openAlertModal("Attention", att, "", "var(--color-warning)"); } }; openPromptModal("Séparation de l'assiduité sélectionnée", fieldSet, success, () => { }, "var(--color-present)"); + // Initialisation du timepicker + const deb = this.selectedAssiduite.date_debut.substring(11,16); + const fin = this.selectedAssiduite.date_fin.substring(11,16); + setTimeout(()=>{ + $('#promptTime').timepicker({ + timeFormat: 'HH:mm', + interval: 60 * tick_delay, + minTime: deb, + startTime: deb, + maxTime: fin, + dynamic: false, + dropdown: true, + scrollbar: false, + + }); + }, 100 + ); } /** @@ -466,4 +475,9 @@ this.editBtn.disabled = true; } } - \ No newline at end of file + + \ No newline at end of file diff --git a/app/templates/assiduites/widgets/minitimeline.j2 b/app/templates/assiduites/widgets/minitimeline.j2 index 335ac701..aa2172bc 100644 --- a/app/templates/assiduites/widgets/minitimeline.j2 +++ b/app/templates/assiduites/widgets/minitimeline.j2 @@ -157,6 +157,11 @@ stateDiv.textContent = `État: ${assiduite.etat.capitalize()}`; bubble.appendChild(stateDiv); + const motifDiv = document.createElement("div"); + stateDiv.className = "assiduite-why"; + stateDiv.textContent = `Motif: ${assiduite.desc?.capitalize()}`; + bubble.appendChild(motifDiv); + const userIdDiv = document.createElement("div"); userIdDiv.className = "assiduite-user_id"; userIdDiv.textContent = `saisie le ${formatDateModal( diff --git a/app/templates/assiduites/widgets/moduleimpl_selector_multiple.j2 b/app/templates/assiduites/widgets/moduleimpl_selector_multiple.j2 new file mode 100644 index 00000000..db77984f --- /dev/null +++ b/app/templates/assiduites/widgets/moduleimpl_selector_multiple.j2 @@ -0,0 +1,20 @@ + \ No newline at end of file diff --git a/app/views/assiduites.py b/app/views/assiduites.py index 84cc3d33..98e9c563 100644 --- a/app/views/assiduites.py +++ b/app/views/assiduites.py @@ -178,62 +178,25 @@ class HTMLBuilder: def bilan_dept(): """Gestionnaire assiduités, page principale""" - # Préparation de la page - H = [ - html_sco_header.sco_header( - page_title="Saisie de l'assiduité", - javascripts=[ - "js/assiduites.js", - "js/date_utils.js", - ], - cssstyles=[ - "css/assiduites.css", - ], - ), - """

Traitement de l'assiduité

-

- Pour saisir l'assiduité ou consulter les états, il est recommandé de passer par - le semestre concerné (saisie par jour ou saisie différée). -

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

Pour signaler, annuler ou justifier l'assiduité d'un seul étudiant, - choisissez d'abord la personne concernée :

""" - ) - # Ajout de la barre de recherche d'étudiant (redirection vers bilan etud) - H.append(sco_find_etud.form_search_etud(dest_url="assiduites.bilan_etud")) - # Gestion des billets d'absences if current_user.has_permission( Permission.AbsChange ) and sco_preferences.get_preference("handle_billets_abs"): - H.append( - f""" + billets = f"""

Billets d'absence

""" - ) - - # Récupération des années d'étude du département - # (afin de sélectionner une année) + else: + billets = "" + # Récupération du département dept: Departement = Departement.query.filter_by(id=g.scodoc_dept_id).first() - annees: list[int] = sorted( - [f.date_debut.year for f in dept.formsemestres], - reverse=True, - ) - annee = scu.annee_scolaire() # Année courante, sera utilisée par défaut - # Génération d'une liste "json" d'années - annees_str: str = "[" - for ann in annees: - annees_str += f"{ann}," - annees_str += "]" # Récupération d'un formsemestre - # (pour n'afficher que les assiduites/justificatifs liés au formsemestre) + # (pour n'afficher que les justificatifs liés au formsemestre) formsemestre_id = request.args.get("formsemestre_id", "") + formsemestre = None if formsemestre_id: try: formsemestre: FormSemestre = FormSemestre.get_formsemestre(formsemestre_id) @@ -241,19 +204,71 @@ def bilan_dept(): except AttributeError: formsemestre_id = "" - # Peuplement du template jinja - H.append( - render_template( - "assiduites/pages/bilan_dept.j2", - dept_id=g.scodoc_dept_id, - annee=annee, - annees=annees_str, - formsemestre_id=formsemestre_id, - group_id=request.args.get("group_id", ""), + # <=> Génération du tableau <=> + + # Récupération des étudiants du département / groupe + etudids: list[int] = [etud.id for etud in dept.etudiants] # cas département + group_ids = request.args.get("group_ids", "") + if group_ids and formsemestre: + groups_infos = sco_groups_view.DisplayedGroupsInfos( + group_ids.split(","), + formsemestre_id=formsemestre.id, + select_all_when_unspecified=True, + ) + + if groups_infos.members: + etudids = [m["etudid"] for m in groups_infos.members] + + # justificatifs (en attente ou modifiés avec les semestres associés) + justificatifs_query: Query = Justificatif.query.filter( + Justificatif.etat.in_( + [scu.EtatJustificatif.ATTENTE, scu.EtatJustificatif.MODIFIE] ), + Justificatif.etudid.in_(etudids), + ) + # Filtrage par semestre si formsemestre_id != "" + if formsemestre: + justificatifs_query = justificatifs_query.filter( + Justificatif.date_debut >= formsemestre.date_debut, + Justificatif.date_debut <= formsemestre.date_fin, + ) + + data = liste_assi.AssiJustifData( + assiduites_query=None, + justificatifs_query=justificatifs_query, + ) + + fname: str = "Bilan Département" + cache_key: str = "tableau-dept" + titre: str = "Justificatifs en attente ou modifiés" + + if formsemestre: + fname += f" {formsemestre.titre_annee()}" + cache_key += f"-{formsemestre.id}" + titre += f" {formsemestre.titre_annee()}" + + if group_ids: + cache_key += f" {group_ids}" + + table = _prepare_tableau( + data, + afficher_etu=True, + filename=fname, + titre=titre, + cache_key=cache_key, + ) + + if not table[0]: + return table[1] + + # Peuplement du template jinja + return render_template( + "assiduites/pages/bilan_dept.j2", + tableau=table[1], + search_etud=sco_find_etud.form_search_etud(dest_url="assiduites.bilan_etud"), + billets=billets, + sco=ScoData(formsemestre=formsemestre), ) - H.append(html_sco_header.sco_footer()) - return "\n".join(H) @bp.route("/ajout_assiduite_etud", methods=["GET", "POST"]) @@ -601,6 +616,29 @@ def bilan_etud(): sco_preferences.get_preference("assi_metrique", dept_id=g.scodoc_dept_id), ) + # 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 + ), + etud.justificatifs.filter( + Justificatif.etat.in_( + [scu.EtatJustificatif.ATTENTE, scu.EtatJustificatif.MODIFIE] + ) + ), + ) + + table = _prepare_tableau( + data, + afficher_etu=False, + filename=f"Bilan assiduité {etud.nomprenom}", + titre="Bilan de l'assiduité de l'étudiant", + cache_key=f"tableau-etud-{etud.id}-bilan", + ) + + if not table[0]: + return table[1] + # Génération de la page return HTMLBuilder( header, @@ -615,6 +653,7 @@ def bilan_etud(): "assi_limit_annee", dept_id=g.scodoc_dept_id, ), + tableau=table[1], ), ).build() @@ -1599,18 +1638,7 @@ def tableau_assiduite_actions(): if obj_type == "assiduite": # 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 - formsemestre = objet.get_formsemestre() - 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" + module = _module_selector_multiple(objet.etudiant, objet.moduleimpl_id) return render_template( "assiduites/pages/tableau_assiduite_actions.j2", @@ -1818,7 +1846,7 @@ def signal_assiduites_diff(): ) date_fin: datetime.date = date_deb + datetime.timedelta(days=6) - etudiants: list[dict] = [] + etudiants: list[Identite] = [] # --- Vérification de la date --- real_date = scu.is_iso_formated(date, True).date() @@ -1846,15 +1874,9 @@ def signal_assiduites_diff(): # Récupération des étudiants etudiants.extend( - [ - sco_etud.get_etud_info(etudid=m["etudid"], filled=True)[0] - for m in groups_infos.members - ] + [Identite.get_etud(etudid=m["etudid"]) for m in groups_infos.members] ) - # 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 - etudiants = list(sorted(etudiants, key=lambda x: x["nom"])) + etudiants = list(sorted(etudiants, key=lambda etud: etud.sort_key)) # Génération de l'HTML @@ -1962,9 +1984,7 @@ def signale_evaluation_abs(etudid: int = None, evaluation_id: int = None): "assiduites.ajout_assiduite_etud", etudid=etudid, evaluation_id=evaluation.id, - date_deb=evaluation.date_debut.strftime( - "%Y-%m-%dT%H:%M:%S" - ), + date_deb=evaluation.date_debut.strftime("%Y-%m-%dT%H:%M:%S"), date_fin=evaluation.date_fin.strftime("%Y-%m-%dT%H:%M:%S"), moduleimpl_id=evaluation.moduleimpl.id, saisie_eval="true", @@ -2234,6 +2254,32 @@ def _module_selector(formsemestre: FormSemestre, moduleimpl_id: int = None) -> s ) +def _module_selector_multiple( + etud: Identite, moduleimpl_id: int = None, only_form: FormSemestre = None +) -> str: + modimpls_by_formsemestre = etud.get_modimpls_by_formsemestre(scu.annee_scolaire()) + choices = {} + for formsemestre_id in modimpls_by_formsemestre: + formsemestre: FormSemestre = FormSemestre.query.get(formsemestre_id) + if only_form is not None and formsemestre != only_form: + continue + # indique le nom du semestre dans le menu (optgroup) + choices[formsemestre.titre_annee()] = [ + { + "moduleimpl_id": m.id, + "name": f"{m.module.code} {m.module.abbrev or m.module.titre or ''}", + } + for m in modimpls_by_formsemestre[formsemestre_id] + if m.module.ue.type == UE_STANDARD + ] + + return render_template( + "assiduites/widgets/moduleimpl_selector_multiple.j2", + choices=choices, + moduleimpl_id=moduleimpl_id, + ) + + def _dynamic_module_selector() -> str: """ _dynamic_module_selector retourne l'html/css/javascript du selecteur de module dynamique @@ -2630,6 +2676,8 @@ def _generate_assiduite_bubble(assiduite: Assiduite) -> str: # Récupérer informations saisie saisie: str = assiduite.get_saisie() + motif: str = assiduite.description if assiduite.description else "" + return render_template( "assiduites/widgets/assiduite_bubble.j2", moduleimpl=moduleimpl_infos, @@ -2637,4 +2685,5 @@ def _generate_assiduite_bubble(assiduite: Assiduite) -> str: date_debut=assiduite.date_debut.strftime("%d/%m/%Y %H:%M"), date_fin=assiduite.date_fin.strftime("%d/%m/%Y %H:%M"), saisie=saisie, + motif=motif, ) diff --git a/tests/unit/test_assiduites.py b/tests/unit/test_assiduites.py index 12b34b80..810dd2a0 100644 --- a/tests/unit/test_assiduites.py +++ b/tests/unit/test_assiduites.py @@ -557,50 +557,65 @@ def verifier_filtrage_justificatifs(etud: Identite, justificatifs: list[Justific assert ( scass.filter_by_date(etud.justificatifs, Justificatif).count() == 5 ), "Filtrage 'Toute Date' mauvais 1" - - date = scu.localize_datetime("2022-09-01T10:00+01:00") + date = scu.localize_datetime( + scu.is_iso_formated("2022-09-01T10:00+01:00", convert=True) + ) assert ( scass.filter_by_date(etud.justificatifs, Justificatif, date_deb=date).count() == 5 ), "Filtrage 'Toute Date' mauvais 2" - date = scu.localize_datetime("2022-09-05T08:00+01:00") + date = scu.localize_datetime( + scu.is_iso_formated("2022-09-05T08:00+01:00", convert=True) + ) assert ( scass.filter_by_date(etud.justificatifs, Justificatif, date_deb=date).count() == 5 ), "Filtrage 'date début' mauvais 3" - date = scu.localize_datetime("2022-09-05T08:00:01+01:00") + date = scu.localize_datetime( + scu.is_iso_formated("2022-09-05T08:00:01+01:00", convert=True) + ) assert ( scass.filter_by_date(etud.justificatifs, Justificatif, date_deb=date).count() == 5 ), "Filtrage 'date début' mauvais 4" - date = scu.localize_datetime("2022-09-05T10:00+01:00") + date = scu.localize_datetime( + scu.is_iso_formated("2022-09-05T10:00+01:00", convert=True) + ) assert ( scass.filter_by_date(etud.justificatifs, Justificatif, date_deb=date).count() == 4 ), "Filtrage 'date début' mauvais 5" - date = scu.localize_datetime("2022-09-01T10:00+01:00") + date = scu.localize_datetime( + scu.is_iso_formated("2022-09-01T10:00+01:00", convert=True) + ) assert ( scass.filter_by_date(etud.justificatifs, Justificatif, date_fin=date).count() == 0 ), "Filtrage 'date fin' mauvais 6" - date = scu.localize_datetime("2022-09-05T08:00+01:00") + date = scu.localize_datetime( + scu.is_iso_formated("2022-09-05T08:00+01:00", convert=True) + ) assert ( scass.filter_by_date(etud.justificatifs, Justificatif, date_fin=date).count() == 1 ), "Filtrage 'date fin' mauvais 7" - date = scu.localize_datetime("2022-09-05T10:00:01+01:00") + date = scu.localize_datetime( + scu.is_iso_formated("2022-09-05T10:00:01+01:00", convert=True) + ) assert ( scass.filter_by_date(etud.justificatifs, Justificatif, date_fin=date).count() == 2 ), "Filtrage 'date fin' mauvais 8" - date = scu.localize_datetime("2023-01-03T12:00+01:00") + date = scu.localize_datetime( + scu.is_iso_formated("2023-01-03T12:00+01:00", convert=True) + ) assert ( scass.filter_by_date(etud.justificatifs, Justificatif, date_fin=date).count() == 5 @@ -624,8 +639,12 @@ def editer_supprimer_justificatif(etud: Identite): # Modification de l'état justi.etat = scu.EtatJustificatif.MODIFIE # Modification du moduleimpl - justi.date_debut = scu.localize_datetime("2023-02-03T11:00:01+01:00") - justi.date_fin = scu.localize_datetime("2023-02-03T12:00:01+01:00") + justi.date_debut = scu.localize_datetime( + scu.is_iso_formated("2023-02-03T11:00:01+01:00", convert=True) + ) + justi.date_fin = scu.localize_datetime( + scu.is_iso_formated("2023-02-03T12:00:01+01:00", convert=True) + ) db.session.add(justi) db.session.commit() @@ -639,7 +658,9 @@ def editer_supprimer_justificatif(etud: Identite): scass.filter_by_date( etud.justificatifs, Justificatif, - date_deb=scu.localize_datetime("2023-02-01T11:00:00+01:00"), + date_deb=scu.localize_datetime( + scu.is_iso_formated("2023-02-01T11:00:00+01:00", convert=True) + ), ).count() == 1 ), "Edition de justificatif mauvais 2" @@ -930,44 +951,60 @@ def verifier_comptage_et_filtrage_assiduites( scass.filter_by_date(etu2.assiduites, Assiduite).count() == 7 ), "Filtrage 'Date début' mauvais 1" - date = scu.localize_datetime("2022-09-01T10:00+01:00") + date = scu.localize_datetime( + scu.is_iso_formated("2022-09-01T10:00+01:00", convert=True) + ) assert ( scass.filter_by_date(etu2.assiduites, Assiduite, date_deb=date).count() == 7 ), "Filtrage 'Date début' mauvais 2" - date = scu.localize_datetime("2022-09-05T10:00+01:00") + date = scu.localize_datetime( + scu.is_iso_formated("2022-09-05T10:00+01:00", convert=True) + ) assert ( scass.filter_by_date(etu2.assiduites, Assiduite, date_deb=date).count() == 7 ), "Filtrage 'Date début' mauvais 3" - date = scu.localize_datetime("2022-09-05T16:00+01:00") + date = scu.localize_datetime( + scu.is_iso_formated("2022-09-05T16:00+01:00", convert=True) + ) assert ( scass.filter_by_date(etu2.assiduites, Assiduite, date_deb=date).count() == 4 ), "Filtrage 'Date début' mauvais 4" # Date Fin - date = scu.localize_datetime("2022-09-01T10:00+01:00") + date = scu.localize_datetime( + scu.is_iso_formated("2022-09-01T10:00+01:00", convert=True) + ) assert ( scass.filter_by_date(etu2.assiduites, Assiduite, date_fin=date).count() == 0 ), "Filtrage 'Date fin' mauvais 1" - date = scu.localize_datetime("2022-09-05T10:00+01:00") + date = scu.localize_datetime( + scu.is_iso_formated("2022-09-05T10:00+01:00", convert=True) + ) assert ( scass.filter_by_date(etu2.assiduites, Assiduite, date_fin=date).count() == 1 ), "Filtrage 'Date fin' mauvais 2" - date = scu.localize_datetime("2022-09-05T10:00:01+01:00") + date = scu.localize_datetime( + scu.is_iso_formated("2022-09-05T10:00:01+01:00", convert=True) + ) assert ( scass.filter_by_date(etu2.assiduites, Assiduite, date_fin=date).count() == 2 ), "Filtrage 'Date fin' mauvais 3" - date = scu.localize_datetime("2022-09-05T16:00+01:00") + date = scu.localize_datetime( + scu.is_iso_formated("2022-09-05T16:00+01:00", convert=True) + ) assert ( scass.filter_by_date(etu2.assiduites, Assiduite, date_fin=date).count() == 3 ), "Filtrage 'Date fin' mauvais 4" - date = scu.localize_datetime("2023-01-04T16:00+01:00") + date = scu.localize_datetime( + scu.is_iso_formated("2023-01-04T16:00+01:00", convert=True) + ) assert ( scass.filter_by_date(etu2.assiduites, Assiduite, date_fin=date).count() == 7 ), "Filtrage 'Date fin' mauvais 5"