diff --git a/app/api/justificatifs.py b/app/api/justificatifs.py
index 611785efd..ff1487a9a 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 b97f44185..82caff299 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 1421ff287..6b65b2791 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 e6d3fa814..e72ee1bd1 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 2b89c1f72..365a44c5e 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:
Justificatifs en attente
diff --git a/app/scodoc/sco_preferences.py b/app/scodoc/sco_preferences.py
index ede0698a8..8aa38e25d 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 a6d5d8b91..9c9ba39e9 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 bd22ecb25..85355a1e8 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 f288faede..24a449b57 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 c38154935..f4a20efb0 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" %}
-
- Attention, cet étudiant a trop d'absences
-
+{% 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 7c1f1c7e1..6f9a320f2 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 ed801d751..d6066a4b1 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 3db6ea25d..23004a866 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 d7c8638eb..908835898 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 335ac7014..aa2172bca 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 000000000..db77984f1
--- /dev/null
+++ b/app/templates/assiduites/widgets/moduleimpl_selector_multiple.j2
@@ -0,0 +1,20 @@
+
+
+ {% with moduleimpl_id=moduleimpl_id %}
+ {% include "assiduites/widgets/simplemoduleimpl_select.j2" %}
+ {% endwith %}
+
+ {% for cat, mods in choices.items() %}
+
+ {% for mod in mods %}
+ {% if mod.moduleimpl_id == moduleimpl_id %}
+ {{mod.name}}
+ {% else %}
+ {{mod.name}}
+ {% endif %}
+ {% endfor %}
+
+ {% endfor %}
+
+
+
\ No newline at end of file
diff --git a/app/views/assiduites.py b/app/views/assiduites.py
index 84cc3d33e..98e9c5634 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 12b34b802..810dd2a00 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"