diff --git a/app/api/assiduites.py b/app/api/assiduites.py index 3c5699c3b..5c55d9a15 100644 --- a/app/api/assiduites.py +++ b/app/api/assiduites.py @@ -383,6 +383,7 @@ def assiduite_create(etudid: int = None): errors[i] = obj else: success[i] = obj + scass.simple_invalidate_cache(data, etud.id) db.session.commit() @@ -439,6 +440,7 @@ def assiduites_create(): errors[i] = obj else: success[i] = obj + scass.simple_invalidate_cache(data) return {"errors": errors, "success": success} @@ -548,6 +550,7 @@ def assiduite_delete(): output["errors"][f"{i}"] = msg else: output["success"][f"{i}"] = {"OK": True} + db.session.commit() return output @@ -556,6 +559,7 @@ def _delete_singular(assiduite_id: int, database): assiduite_unique: Assiduite = Assiduite.query.filter_by(id=assiduite_id).first() if assiduite_unique is None: return (404, "Assiduite non existante") + scass.simple_invalidate_cache(assiduite_unique.to_dict()) database.session.delete(assiduite_unique) return (200, "OK") @@ -632,6 +636,8 @@ def assiduite_edit(assiduite_id: int): db.session.add(assiduite_unique) db.session.commit() + scass.simple_invalidate_cache(assiduite_unique.to_dict()) + return {"OK": True} @@ -727,6 +733,7 @@ def _edit_singular(assiduite_unique, data): return (404, err) db.session.add(assiduite_unique) + scass.simple_invalidate_cache(assiduite_unique.to_dict()) return (200, "OK") diff --git a/app/api/justificatifs.py b/app/api/justificatifs.py index 52ddee04e..2ef874a62 100644 --- a/app/api/justificatifs.py +++ b/app/api/justificatifs.py @@ -113,7 +113,7 @@ def justificatifs(etudid: int = None, with_query: bool = False): @scodoc @as_json @permission_required(Permission.ScoView) -def justificatifs_dept(dept_id: int = None, with_query : bool = False): +def justificatifs_dept(dept_id: int = None, with_query: bool = False): """ """ dept = Departement.query.get_or_404(dept_id) etuds = [etud.id for etud in dept.etudiants] @@ -172,6 +172,7 @@ def justif_create(etudid: int = None): errors[i] = obj else: success[i] = obj + scass.simple_invalidate_cache(data, etud.id) compute_assiduites_justified(Justificatif.query.filter_by(etudid=etudid), True) return {"errors": errors, "success": success} @@ -323,7 +324,7 @@ def justif_edit(justif_id: int): db.session.add(justificatif_unique) db.session.commit() - return { + retour = { "couverture": { "avant": avant_ids, "après": compute_assiduites_justified( @@ -333,6 +334,9 @@ def justif_edit(justif_id: int): } } + scass.simple_invalidate_cache(justificatif_unique.to_dict()) + return retour + @bp.route("/justificatif/delete", methods=["POST"]) @api_web_bp.route("/justificatif/delete", methods=["POST"]) @@ -366,6 +370,7 @@ def justif_delete(): output["errors"][f"{i}"] = msg else: output["success"][f"{i}"] = {"OK": True} + db.session.commit() return output @@ -389,6 +394,8 @@ def _delete_singular(justif_id: int, database): Justificatif.query.filter_by(etudid=justificatif_unique.etudid), True ) + scass.simple_invalidate_cache(justificatif_unique.to_dict()) + return (200, "OK") diff --git a/app/models/formsemestre.py b/app/models/formsemestre.py index d276f1eb3..37e09d140 100644 --- a/app/models/formsemestre.py +++ b/app/models/formsemestre.py @@ -686,10 +686,10 @@ class FormSemestre(db.Model): tuple (nb abs, nb abs justifiées) Utilise un cache. """ - from app.scodoc import sco_abs + from app.scodoc import sco_assiduites metrique = sco_preferences.get_preference("assi_metrique", self.id) - return sco_abs.get_assiduites_count_in_interval( + return sco_assiduites.get_assiduites_count_in_interval( etudid, self.date_debut.isoformat(), self.date_fin.isoformat(), diff --git a/app/scodoc/html_sidebar.py b/app/scodoc/html_sidebar.py index ad720791a..153e8f6f4 100755 --- a/app/scodoc/html_sidebar.py +++ b/app/scodoc/html_sidebar.py @@ -76,7 +76,7 @@ def sidebar_common(): def sidebar(etudid: int = None): "Main HTML page sidebar" # rewritten from legacy DTML code - from app.scodoc import sco_abs + from app.scodoc import sco_assiduites from app.scodoc import sco_etud params = {} @@ -116,7 +116,7 @@ def sidebar(etudid: int = None): ) if etud["cursem"]: cur_sem = etud["cursem"] - nbabs, nbabsjust = sco_abs.get_assiduites_count(etudid, cur_sem) + nbabs, nbabsjust = sco_assiduites.get_assiduites_count(etudid, cur_sem) nbabsnj = nbabs - nbabsjust H.append( f"""({sco_preferences.get_preference("assi_metrique", None)}) diff --git a/app/scodoc/sco_abs.py b/app/scodoc/sco_abs.py index f34ab6d33..1e56ca87e 100755 --- a/app/scodoc/sco_abs.py +++ b/app/scodoc/sco_abs.py @@ -42,8 +42,6 @@ from app.scodoc import sco_cache from app.scodoc import sco_etud from app.scodoc import sco_formsemestre_inscriptions from app.scodoc import sco_preferences -from app.models import Assiduite, Justificatif -import app.scodoc.sco_assiduites as scass import app.scodoc.sco_utils as scu # --- Misc tools.... ------------------ @@ -1054,68 +1052,6 @@ def get_abs_count_in_interval(etudid, date_debut_iso, date_fin_iso): return r -def get_assiduites_count(etudid, sem): - """Les comptes d'absences de cet étudiant dans ce semestre: - tuple (nb abs non justifiées, nb abs justifiées) - Utilise un cache. - """ - metrique = sco_preferences.get_preference("assi_metrique", sem["formsemestre_id"]) - return get_assiduites_count_in_interval( - etudid, - sem["date_debut_iso"], - sem["date_fin_iso"], - scu.translate_assiduites_metric(metrique), - ) - - -def get_assiduites_count_in_interval( - etudid, date_debut_iso, date_fin_iso, metrique="demi" -): - """Les comptes d'absences de cet étudiant entre ces deux dates, incluses: - tuple (nb abs, nb abs justifiées) - Utilise un cache. - """ - key = ( - str(etudid) - + "_" - + date_debut_iso - + "_" - + date_fin_iso - + f"{metrique}_assiduites" - ) - r = sco_cache.AbsSemEtudCache.get(key) - if not r: - date_debut: datetime.datetime = scu.is_iso_formated(date_debut_iso, True) - date_fin: datetime.datetime = scu.is_iso_formated(date_fin_iso, True) - - assiduites: Assiduite = Assiduite.query.filter_by(etudid=etudid) - assiduites = assiduites.filter(Assiduite.etat != 0) - justificatifs: Justificatif = Justificatif.query.filter_by(etudid=etudid) - - assiduites = scass.filter_by_date(assiduites, Assiduite, date_debut, date_fin) - justificatifs = scass.filter_by_date( - justificatifs, Justificatif, date_debut, date_fin - ) - - calculator: scass.CountCalculator = scass.CountCalculator() - calculator.compute_assiduites(assiduites) - nb_abs: dict = calculator.to_dict()[metrique] - - abs_just: list[Assiduite] = scass.get_all_justified( - etudid, date_debut, date_fin - ) - - calculator.reset() - calculator.compute_assiduites(abs_just) - nb_abs_just: dict = calculator.to_dict()[metrique] - - r = (nb_abs, nb_abs_just) - ans = sco_cache.AbsSemEtudCache.set(key, r) - if not ans: - log("warning: get_assiduites_count failed to cache") - return r - - def invalidate_abs_count(etudid, sem): """Invalidate (clear) cached counts""" date_debut = sem["date_debut_iso"] @@ -1124,15 +1060,6 @@ def invalidate_abs_count(etudid, sem): sco_cache.AbsSemEtudCache.delete(key) -def invalidate_assiduites_count(etudid, sem): - """Invalidate (clear) cached counts""" - date_debut = sem["date_debut_iso"] - date_fin = sem["date_fin_iso"] - for met in ["demi", "journee", "compte", "heure"]: - key = str(etudid) + "_" + date_debut + "_" + date_fin + f"{met}_assiduites" - sco_cache.AbsSemEtudCache.delete(key) - - def invalidate_abs_count_sem(sem): """Invalidate (clear) cached abs counts for all the students of this semestre""" inscriptions = ( @@ -1144,17 +1071,6 @@ def invalidate_abs_count_sem(sem): invalidate_abs_count(ins["etudid"], sem) -def invalidate_assiduites_count_sem(sem): - """Invalidate (clear) cached abs counts for all the students of this semestre""" - inscriptions = ( - sco_formsemestre_inscriptions.do_formsemestre_inscription_listinscrits( - sem["formsemestre_id"] - ) - ) - for ins in inscriptions: - invalidate_assiduites_count(ins["etudid"], sem) - - def invalidate_abs_etud_date(etudid, date): # was invalidateAbsEtudDate """Doit etre appelé à chaque modification des absences pour cet étudiant et cette date. Invalide cache absence et caches semestre @@ -1188,38 +1104,3 @@ def invalidate_abs_etud_date(etudid, date): # was invalidateAbsEtudDate # Inval cache compteurs absences: invalidate_abs_count_sem(sem) - - -def invalidate_assiduites_etud_date(etudid, date): - """Doit etre appelé à chaque modification des assiduites pour cet étudiant et cette date. - Invalide cache absence et caches semestre - date: date au format ISO - """ - from app.scodoc import sco_compute_moy - - # Semestres a cette date: - etud = sco_etud.get_etud_info(etudid=etudid, filled=True)[0] - sems = [ - sem - for sem in etud["sems"] - if sem["date_debut_iso"] <= date and sem["date_fin_iso"] >= date - ] - - # Invalide les PDF et les absences: - for sem in sems: - # Inval cache bulletin et/ou note_table - if sco_compute_moy.formsemestre_expressions_use_abscounts( - sem["formsemestre_id"] - ): - # certaines formules utilisent les absences - pdfonly = False - else: - # efface toujours le PDF car il affiche en général les absences - pdfonly = True - - sco_cache.invalidate_formsemestre( - formsemestre_id=sem["formsemestre_id"], pdfonly=pdfonly - ) - - # Inval cache compteurs absences: - invalidate_assiduites_count(etudid, sem) diff --git a/app/scodoc/sco_abs_notification.py b/app/scodoc/sco_abs_notification.py index 88d1d785e..7ff95d772 100644 --- a/app/scodoc/sco_abs_notification.py +++ b/app/scodoc/sco_abs_notification.py @@ -56,13 +56,13 @@ def abs_notify(etudid, date): (s'il n'y a pas de semestre courant, ne fait rien, car l'etudiant n'est pas inscrit au moment de l'absence!). """ - from app.scodoc import sco_abs + from app.scodoc import sco_assiduites formsemestre = retreive_current_formsemestre(etudid, date) if not formsemestre: return # non inscrit a la date, pas de notification - nbabs, nbabsjust = sco_abs.get_assiduites_count_in_interval( + nbabs, nbabsjust = sco_assiduites.get_assiduites_count_in_interval( etudid, formsemestre.date_debut.isoformat(), formsemestre.date_fin.isoformat(), diff --git a/app/scodoc/sco_assiduites.py b/app/scodoc/sco_assiduites.py index 4af613a59..c9cb30054 100644 --- a/app/scodoc/sco_assiduites.py +++ b/app/scodoc/sco_assiduites.py @@ -2,11 +2,16 @@ Ecrit par Matthias Hartmann. """ from datetime import date, datetime, time, timedelta +from pytz import UTC import app.scodoc.sco_utils as scu from app.models.assiduites import Assiduite, Justificatif from app.models.etudiants import Identite from app.models.formsemestre import FormSemestre, FormSemestreInscription +from app.scodoc import sco_formsemestre_inscriptions +from app.scodoc import sco_preferences +from app.scodoc import sco_cache +from app.scodoc import sco_etud class CountCalculator: @@ -350,3 +355,139 @@ def get_all_justified( date_fin, ) return after + + +# Gestion du cache +def get_assiduites_count(etudid, sem): + """Les comptes d'absences de cet étudiant dans ce semestre: + tuple (nb abs non justifiées, nb abs justifiées) + Utilise un cache. + """ + metrique = sco_preferences.get_preference("assi_metrique", sem["formsemestre_id"]) + return get_assiduites_count_in_interval( + etudid, + sem["date_debut_iso"], + sem["date_fin_iso"], + scu.translate_assiduites_metric(metrique), + ) + + +def get_assiduites_count_in_interval( + etudid, date_debut_iso, date_fin_iso, metrique="demi" +): + """Les comptes d'absences de cet étudiant entre ces deux dates, incluses: + tuple (nb abs, nb abs justifiées) + Utilise un cache. + """ + key = ( + str(etudid) + + "_" + + date_debut_iso + + "_" + + date_fin_iso + + f"{metrique}_assiduites" + ) + r = sco_cache.AbsSemEtudCache.get(key) + if not r: + date_debut: datetime.datetime = scu.is_iso_formated(date_debut_iso, True) + date_fin: datetime.datetime = scu.is_iso_formated(date_fin_iso, True) + + assiduites: Assiduite = Assiduite.query.filter_by(etudid=etudid) + assiduites = assiduites.filter(Assiduite.etat == scu.EtatAssiduite.ABSENT) + justificatifs: Justificatif = Justificatif.query.filter_by(etudid=etudid) + + assiduites = filter_by_date(assiduites, Assiduite, date_debut, date_fin) + justificatifs = filter_by_date( + justificatifs, Justificatif, date_debut, date_fin + ) + + calculator: CountCalculator = CountCalculator() + calculator.compute_assiduites(assiduites) + nb_abs: dict = calculator.to_dict()[metrique] + + abs_just: list[Assiduite] = get_all_justified(etudid, date_debut, date_fin) + + calculator.reset() + calculator.compute_assiduites(abs_just) + nb_abs_just: dict = calculator.to_dict()[metrique] + + r = (nb_abs, nb_abs_just) + ans = sco_cache.AbsSemEtudCache.set(key, r) + if not ans: + log("warning: get_assiduites_count failed to cache") + return r + + +def invalidate_assiduites_count(etudid, sem): + """Invalidate (clear) cached counts""" + date_debut = sem["date_debut_iso"] + date_fin = sem["date_fin_iso"] + for met in ["demi", "journee", "compte", "heure"]: + key = str(etudid) + "_" + date_debut + "_" + date_fin + f"{met}_assiduites" + sco_cache.AbsSemEtudCache.delete(key) + + +def invalidate_assiduites_count_sem(sem): + """Invalidate (clear) cached abs counts for all the students of this semestre""" + inscriptions = ( + sco_formsemestre_inscriptions.do_formsemestre_inscription_listinscrits( + sem["formsemestre_id"] + ) + ) + for ins in inscriptions: + invalidate_assiduites_count(ins["etudid"], sem) + + +def invalidate_assiduites_etud_date(etudid, date: datetime): + """Doit etre appelé à chaque modification des assiduites pour cet étudiant et cette date. + Invalide cache absence et caches semestre + date: date au format ISO + """ + from app.scodoc import sco_compute_moy + + # Semestres a cette date: + etud = sco_etud.get_etud_info(etudid=etudid, filled=True)[0] + sems = [ + sem + for sem in etud["sems"] + if scu.is_iso_formated(sem["date_debut_iso"], True).replace(tzinfo=UTC) + <= date.replace(tzinfo=UTC) + and scu.is_iso_formated(sem["date_fin_iso"], True).replace(tzinfo=UTC) + >= date.replace(tzinfo=UTC) + ] + + # Invalide les PDF et les absences: + for sem in sems: + # Inval cache bulletin et/ou note_table + if sco_compute_moy.formsemestre_expressions_use_abscounts( + sem["formsemestre_id"] + ): + # certaines formules utilisent les absences + pdfonly = False + else: + # efface toujours le PDF car il affiche en général les absences + pdfonly = True + + sco_cache.invalidate_formsemestre( + formsemestre_id=sem["formsemestre_id"], pdfonly=pdfonly + ) + + # Inval cache compteurs absences: + invalidate_assiduites_count(etudid, sem) + + +def simple_invalidate_cache(obj: dict, etudid: str or int = None): + """Invalide le cache de l'étudiant et du / des semestres""" + date_debut = ( + obj["date_debut"] + if isinstance(obj["date_debut"], datetime) + else scu.is_iso_formated(obj["date_debut"], True) + ) + date_fin = ( + obj["date_fin"] + if isinstance(obj["date_fin"], datetime) + else scu.is_iso_formated(obj["date_fin"], True) + ) + etudid = etudid if etudid is not None else obj["etudid"] + invalidate_assiduites_etud_date(etudid, date_debut) + invalidate_assiduites_etud_date(etudid, date_fin) diff --git a/app/scodoc/sco_bulletins.py b/app/scodoc/sco_bulletins.py index d8f62b983..9c64eee2e 100644 --- a/app/scodoc/sco_bulletins.py +++ b/app/scodoc/sco_bulletins.py @@ -56,7 +56,7 @@ from app.scodoc.sco_permissions import Permission from app.scodoc.sco_exceptions import AccessDenied, ScoValueError from app.scodoc import html_sco_header from app.scodoc import htmlutils -from app.scodoc import sco_abs +from app.scodoc import sco_assiduites from app.scodoc import sco_abs_views from app.scodoc import sco_bulletins_generator from app.scodoc import sco_bulletins_json @@ -142,7 +142,7 @@ def formsemestre_bulletinetud_dict(formsemestre_id, etudid, version="long"): Cette fonction est utilisée pour les bulletins CLASSIQUES (DUT, ...) en HTML et PDF, mais pas ceux en XML. """ - from app.scodoc import sco_abs + from app.scodoc import sco_assiduites if version not in scu.BULLETINS_VERSIONS: raise ValueError("invalid version code !") @@ -197,7 +197,7 @@ def formsemestre_bulletinetud_dict(formsemestre_id, etudid, version="long"): pid = partition["partition_id"] partitions_etud_groups[pid] = sco_groups.get_etud_groups_in_partition(pid) # --- Absences - I["nbabs"], I["nbabsjust"] = sco_abs.get_assiduites_count(etudid, nt.sem) + I["nbabs"], I["nbabsjust"] = sco_assiduites.get_assiduites_count(etudid, nt.sem) # --- Decision Jury infos, dpv = etud_descr_situation_semestre( @@ -487,7 +487,7 @@ def _ue_mod_bulletin( ) # peut etre 'NI' is_malus = mod["module"]["module_type"] == ModuleType.MALUS if bul_show_abs_modules: - nbabs, nbabsjust = sco_abs.get_assiduites_count(etudid, sem) + nbabs, nbabsjust = sco_assiduites.get_assiduites_count(etudid, sem) mod_abs = [nbabs, nbabsjust] mod["mod_abs_txt"] = scu.fmt_abs(mod_abs) else: diff --git a/app/scodoc/sco_bulletins_json.py b/app/scodoc/sco_bulletins_json.py index ec03dec72..8d592eb37 100644 --- a/app/scodoc/sco_bulletins_json.py +++ b/app/scodoc/sco_bulletins_json.py @@ -43,7 +43,7 @@ from app.models.formsemestre import FormSemestre import app.scodoc.sco_utils as scu import app.scodoc.notesdb as ndb -from app.scodoc import sco_abs +from app.scodoc import sco_assiduites from app.scodoc import sco_edit_ue from app.scodoc import sco_evaluations from app.scodoc import sco_evaluation_db @@ -297,7 +297,7 @@ def formsemestre_bulletinetud_published_dict( # --- Absences if prefs["bul_show_abs"]: - nbabs, nbabsjust = sco_abs.get_assiduites_count(etudid, sem) + nbabs, nbabsjust = sco_assiduites.get_assiduites_count(etudid, sem) d["absences"] = dict(nbabs=nbabs, nbabsjust=nbabsjust) # --- Décision Jury diff --git a/app/scodoc/sco_bulletins_xml.py b/app/scodoc/sco_bulletins_xml.py index 71845c666..178b02964 100644 --- a/app/scodoc/sco_bulletins_xml.py +++ b/app/scodoc/sco_bulletins_xml.py @@ -51,7 +51,7 @@ import app.scodoc.notesdb as ndb from app import log from app.but.bulletin_but_xml_compat import bulletin_but_xml_compat from app.models.formsemestre import FormSemestre -from app.scodoc import sco_abs +from app.scodoc import sco_assiduites from app.scodoc import codes_cursus from app.scodoc import sco_edit_ue from app.scodoc import sco_evaluation_db @@ -370,7 +370,7 @@ def make_xml_formsemestre_bulletinetud( # --- Absences if sco_preferences.get_preference("bul_show_abs", formsemestre_id): - nbabs, nbabsjust = sco_abs.get_assiduites_count(etudid, sem) + nbabs, nbabsjust = sco_assiduites.get_assiduites_count(etudid, sem) doc.append(Element("absences", nbabs=str(nbabs), nbabsjust=str(nbabsjust))) # --- Decision Jury if ( diff --git a/app/scodoc/sco_formsemestre_validation.py b/app/scodoc/sco_formsemestre_validation.py index bf7eb46f8..f145b3f2c 100644 --- a/app/scodoc/sco_formsemestre_validation.py +++ b/app/scodoc/sco_formsemestre_validation.py @@ -53,7 +53,7 @@ from app.scodoc.codes_cursus import * from app.scodoc.TrivialFormulator import TrivialFormulator, tf_error_message from app.scodoc import html_sco_header -from app.scodoc import sco_abs +from app.scodoc import sco_assiduites from app.scodoc import codes_cursus from app.scodoc import sco_cache from app.scodoc import sco_edit_ue @@ -702,7 +702,7 @@ def formsemestre_recap_parcours_table( f"""