diff --git a/app/api/jury.py b/app/api/jury.py index 2800c77a99da18b0d4db3b2c0076c09191468aab..7de6cdef08fd42f240125b43eabc85804ecf86a1 100644 --- a/app/api/jury.py +++ b/app/api/jury.py @@ -5,9 +5,10 @@ ############################################################################## """ - ScoDoc 9 API : jury WIP + ScoDoc 9 API : jury WIP à compléter avec enregistrement décisions """ +from flask import g, url_for from flask_json import as_json from flask_login import login_required @@ -24,6 +25,7 @@ from app.models import ( Identite, ScolarAutorisationInscription, ScolarFormSemestreValidation, + ScolarNews, ) from app.scodoc import sco_cache from app.scodoc.sco_permissions import Permission @@ -47,6 +49,20 @@ def decisions_jury(formsemestre_id: int): raise ScoException("non implemente") +def _news_delete_jury_etud(etud: Identite): + "génère news sur effacement décision" + # n'utilise pas g.scodoc_dept, pas toujours dispo en mode API + url = url_for( + "scolar.ficheEtud", scodoc_dept=etud.departement.acronym, etudid=etud.id + ) + ScolarNews.add( + typ=ScolarNews.NEWS_JURY, + obj=etud.id, + text=f"""Suppression décision jury pour <a href="{url}">{etud.nomprenom}</a>""", + url=url, + ) + + @bp.route( "/etudiant/<int:etudid>/jury/validation_ue/<int:validation_id>/delete", methods=["POST"], @@ -94,6 +110,7 @@ def _validation_ue_delete(etudid: int, validation_id: int): db.session.delete(validation) sco_cache.invalidate_formsemestre_etud(etud) db.session.commit() + _news_delete_jury_etud(etud) return "ok" @@ -121,6 +138,7 @@ def autorisation_inscription_delete(etudid: int, validation_id: int): db.session.delete(validation) sco_cache.invalidate_formsemestre_etud(etud) db.session.commit() + _news_delete_jury_etud(etud) return "ok" @@ -148,6 +166,7 @@ def validation_rcue_delete(etudid: int, validation_id: int): db.session.delete(validation) sco_cache.invalidate_formsemestre_etud(etud) db.session.commit() + _news_delete_jury_etud(etud) return "ok" @@ -175,4 +194,5 @@ def validation_annee_but_delete(etudid: int, validation_id: int): db.session.delete(validation) sco_cache.invalidate_formsemestre_etud(etud) db.session.commit() + _news_delete_jury_etud(etud) return "ok" diff --git a/app/but/jury_but.py b/app/but/jury_but.py index d9f8031a2f61d9bd2d725051bedb868f2b77cfee..1a5c1a4c662d11f41d542ed6b2a5dff146642974 100644 --- a/app/but/jury_but.py +++ b/app/but/jury_but.py @@ -459,10 +459,7 @@ class DecisionsProposeesAnnee(DecisionsProposees): """informations, for debugging purpose.""" text = f"""<b>DecisionsProposeesAnnee</b> <ul> - <li>Etudiant: <a href="{url_for("scolar.ficheEtud", - scodoc_dept=g.scodoc_dept, etudid=self.etud.id) - }">{self.etud.nomprenom}</a> - </li> + <li>Étudiant: {self.etud.html_link_fiche()}</li> """ for formsemestre, title in ( (self.formsemestre_impair, "formsemestre_impair"), diff --git a/app/but/jury_but_validation_auto.py b/app/but/jury_but_validation_auto.py index e698b5dd40d6768882340f5861ef0417baaea68f..a061b42f11bb3b797c26eddb6ad6c50b7bbee00f 100644 --- a/app/but/jury_but_validation_auto.py +++ b/app/but/jury_but_validation_auto.py @@ -6,11 +6,11 @@ """Jury BUT: calcul des décisions de jury annuelles "automatiques" """ +from flask import g, url_for from app import db from app.but import jury_but -from app.models.etudiants import Identite -from app.models.formsemestre import FormSemestre +from app.models import Identite, FormSemestre, ScolarNews from app.scodoc import sco_cache from app.scodoc.sco_exceptions import ScoValueError @@ -39,4 +39,14 @@ def formsemestre_validation_auto_but( nb_etud_modif += deca.record_all(only_validantes=only_adm) db.session.commit() + ScolarNews.add( + typ=ScolarNews.NEWS_JURY, + obj=formsemestre.id, + text=f"""Calcul jury automatique du semestre {formsemestre.html_link_status()}""", + url=url_for( + "notes.formsemestre_status", + scodoc_dept=g.scodoc_dept, + formsemestre_id=formsemestre.id, + ), + ) return nb_etud_modif diff --git a/app/but/jury_but_view.py b/app/but/jury_but_view.py index 87321684e2613b534e3436d64616c5999e907be4..275d93b1e3f6ee4197a31ba146a6b2bfe88d52cb 100644 --- a/app/but/jury_but_view.py +++ b/app/but/jury_but_view.py @@ -31,6 +31,7 @@ from app.models import ( UniteEns, ScolarAutorisationInscription, ScolarFormSemestreValidation, + ScolarNews, ) from app.models.config import ScoDocSiteConfig from app.scodoc import html_sco_header @@ -369,6 +370,16 @@ def jury_but_semestriel( flash( f"autorisation de passage en S{formsemestre.semestre_id + 1} annulée" ) + ScolarNews.add( + typ=ScolarNews.NEWS_JURY, + obj=formsemestre.id, + text=f"""Saisie décision jury dans {formsemestre.html_link_status()}""", + url=url_for( + "notes.formsemestre_status", + scodoc_dept=g.scodoc_dept, + formsemestre_id=formsemestre.id, + ), + ) return flask.redirect( url_for( "notes.formsemestre_validation_but", diff --git a/app/models/events.py b/app/models/events.py index 6555948d60b199193a177dcd18aeae83ae2b240e..d3e7709761262dc6db73f1465eec99862d3fed5b 100644 --- a/app/models/events.py +++ b/app/models/events.py @@ -54,14 +54,17 @@ class ScolarNews(db.Model): NEWS_APO = "APO" # changements de codes APO NEWS_FORM = "FORM" # modification formation (object=formation_id) NEWS_INSCR = "INSCR" # inscription d'étudiants (object=None ou formsemestre_id) + NEWS_JURY = "JURY" # saisie jury NEWS_MISC = "MISC" # unused NEWS_NOTE = "NOTES" # saisie note (object=moduleimpl_id) NEWS_SEM = "SEM" # creation semestre (object=None) + NEWS_MAP = { NEWS_ABS: "saisie absence", NEWS_APO: "modif. code Apogée", NEWS_FORM: "modification formation", NEWS_INSCR: "inscription d'étudiants", + NEWS_JURY: "saisie jury", NEWS_MISC: "opération", # unused NEWS_NOTE: "saisie note", NEWS_SEM: "création semestre", @@ -130,10 +133,10 @@ class ScolarNews(db.Model): return query.order_by(cls.date.desc()).limit(n).all() @classmethod - def add(cls, typ, obj=None, text="", url=None, max_frequency=0): + def add(cls, typ, obj=None, text="", url=None, max_frequency=600): """Enregistre une nouvelle Si max_frequency, ne génère pas 2 nouvelles "identiques" - à moins de max_frequency secondes d'intervalle. + à moins de max_frequency secondes d'intervalle (10 minutes par défaut). Deux nouvelles sont considérées comme "identiques" si elles ont même (obj, typ, user). La nouvelle enregistrée est aussi envoyée par mail. @@ -153,7 +156,10 @@ class ScolarNews(db.Model): if last_news: now = datetime.datetime.now(tz=last_news.date.tzinfo) if (now - last_news.date) < datetime.timedelta(seconds=max_frequency): - # on n'enregistre pas + # pas de nouvel event, mais met à jour l'heure + last_news.date = datetime.datetime.now() + db.session.add(last_news) + db.session.commit() return news = ScolarNews( diff --git a/app/scodoc/sco_edit_formation.py b/app/scodoc/sco_edit_formation.py index 384ec0650c7876f567fc4b8d0bcb91b75ce5f79b..a3ce002a0ac76010b8be811127e0e041f40f60b8 100644 --- a/app/scodoc/sco_edit_formation.py +++ b/app/scodoc/sco_edit_formation.py @@ -132,6 +132,7 @@ def do_formation_delete(formation_id): typ=ScolarNews.NEWS_FORM, obj=formation_id, text=f"Suppression de la formation {acronyme}", + max_frequency=0, ) @@ -329,6 +330,7 @@ def do_formation_create(args: dict) -> Formation: typ=ScolarNews.NEWS_FORM, text=f"""Création de la formation { formation.titre} ({formation.acronyme}) version {formation.version}""", + max_frequency=0, ) return formation diff --git a/app/scodoc/sco_edit_matiere.py b/app/scodoc/sco_edit_matiere.py index 312f01ec120419eb7dbf865829a32e2e99d23f4c..ef425b49382b65216c4b2c9a31819b1d765440e5 100644 --- a/app/scodoc/sco_edit_matiere.py +++ b/app/scodoc/sco_edit_matiere.py @@ -93,7 +93,6 @@ def do_matiere_create(args): typ=ScolarNews.NEWS_FORM, obj=ue["formation_id"], text=f"Modification de la formation {formation.acronyme}", - max_frequency=10 * 60, ) formation.invalidate_cached_sems() return r @@ -199,7 +198,6 @@ def do_matiere_delete(oid): typ=ScolarNews.NEWS_FORM, obj=ue["formation_id"], text=f"Modification de la formation {formation.acronyme}", - max_frequency=10 * 60, ) formation.invalidate_cached_sems() diff --git a/app/scodoc/sco_edit_module.py b/app/scodoc/sco_edit_module.py index 4d963c6e490c2d02accb3fbf5d984778592b6127..139afa8412e166241e606d8d44922e690e96e908 100644 --- a/app/scodoc/sco_edit_module.py +++ b/app/scodoc/sco_edit_module.py @@ -114,7 +114,6 @@ def do_module_create(args) -> int: typ=ScolarNews.NEWS_FORM, obj=formation.id, text=f"Modification de la formation {formation.acronyme}", - max_frequency=10 * 60, ) formation.invalidate_cached_sems() return module_id @@ -186,7 +185,6 @@ def do_module_delete(oid): typ=ScolarNews.NEWS_FORM, obj=mod["formation_id"], text=f"Modification de la formation {formation.acronyme}", - max_frequency=10 * 60, ) formation.invalidate_cached_sems() diff --git a/app/scodoc/sco_edit_ue.py b/app/scodoc/sco_edit_ue.py index ba6cb918eeb2f3b045657438418e8b1fe9f9a7ef..fbe8e2be9611c664182b8f0dcf8d072444d39e06 100644 --- a/app/scodoc/sco_edit_ue.py +++ b/app/scodoc/sco_edit_ue.py @@ -145,7 +145,6 @@ def do_ue_create(args): typ=ScolarNews.NEWS_FORM, obj=args["formation_id"], text=f"Modification de la formation {formation.acronyme}", - max_frequency=10 * 60, ) formation.invalidate_cached_sems() return ue_id @@ -230,7 +229,6 @@ def do_ue_delete(ue: UniteEns, delete_validations=False, force=False): typ=ScolarNews.NEWS_FORM, obj=formation.id, text=f"Modification de la formation {formation.acronyme}", - max_frequency=10 * 60, ) # if not force: diff --git a/app/scodoc/sco_etud.py b/app/scodoc/sco_etud.py index 7bbf12b5f251e8dfe3ca8b462e845bcbd7924d96..827363c89d8fcddd8428cefee9d92bfa9648decc 100644 --- a/app/scodoc/sco_etud.py +++ b/app/scodoc/sco_etud.py @@ -671,6 +671,7 @@ def create_etud(cnx, args: dict = None): typ=ScolarNews.NEWS_INSCR, text='Nouvel étudiant <a href="%(url)s">%(nomprenom)s</a>' % etud, url=etud["url"], + max_frequency=0, ) return etud diff --git a/app/scodoc/sco_formations.py b/app/scodoc/sco_formations.py index 981908c971271da3f9daca56556f7e23e18f28ed..398ed7d47ee4d0f4c7e8b7a46dc731dd42cddd20 100644 --- a/app/scodoc/sco_formations.py +++ b/app/scodoc/sco_formations.py @@ -638,6 +638,7 @@ def formation_create_new_version(formation_id, redirect=True): typ=ScolarNews.NEWS_FORM, obj=new_id, text=f"Nouvelle version de la formation {formation.acronyme}", + max_frequency=0, ) if redirect: flash("Nouvelle version !") diff --git a/app/scodoc/sco_formsemestre.py b/app/scodoc/sco_formsemestre.py index 3d46b1de78d26150af54972f68db4c8ab4ade5f3..bac3352a71898f4a97c2bfb176f34047c2452c4b 100644 --- a/app/scodoc/sco_formsemestre.py +++ b/app/scodoc/sco_formsemestre.py @@ -261,6 +261,7 @@ def do_formsemestre_create(args, silent=False): typ=ScolarNews.NEWS_SEM, text='Création du semestre <a href="%(url)s">%(titre)s</a>' % args, url=args["url"], + max_frequency=0, ) return formsemestre_id diff --git a/app/scodoc/sco_formsemestre_edit.py b/app/scodoc/sco_formsemestre_edit.py index 4961e8a21e40bf647b04d1f4394e4a0797c73d1a..4a94f0bde8ff9877d287ee681ff6df46e03473fd 100644 --- a/app/scodoc/sco_formsemestre_edit.py +++ b/app/scodoc/sco_formsemestre_edit.py @@ -1521,6 +1521,7 @@ def do_formsemestre_delete(formsemestre_id): typ=ScolarNews.NEWS_SEM, obj=formsemestre_id, text="Suppression du semestre %(titre)s" % sem, + max_frequency=0, ) diff --git a/app/scodoc/sco_formsemestre_validation.py b/app/scodoc/sco_formsemestre_validation.py index 1822e3a6555b3ee526adb4474fff905c766a5348..15254a0e5e0a530a9d0ba22d8600479efa7a8515 100644 --- a/app/scodoc/sco_formsemestre_validation.py +++ b/app/scodoc/sco_formsemestre_validation.py @@ -39,7 +39,7 @@ from app import db, log from app.comp import res_sem from app.comp.res_compat import NotesTableCompat -from app.models import Formation, FormSemestre, UniteEns +from app.models import Formation, FormSemestre, UniteEns, ScolarNews from app.models.notes import etud_has_notes_attente from app.models.validations import ( ScolarAutorisationInscription, @@ -992,16 +992,26 @@ def do_formsemestre_validation_auto(formsemestre_id): ) nb_valid += 1 log( - "do_formsemestre_validation_auto: %d validations, %d conflicts" - % (nb_valid, len(conflicts)) + f"do_formsemestre_validation_auto: {nb_valid} validations, {len(conflicts)} conflicts" ) - H = [html_sco_header.sco_header(page_title="Saisie automatique")] - H.append( - """<h2>Saisie automatique des décisions du semestre %s</h2> - <p>Opération effectuée.</p> - <p>%d étudiants validés (sur %s)</p>""" - % (sem["titreannee"], nb_valid, len(etudids)) + ScolarNews.add( + typ=ScolarNews.NEWS_JURY, + obj=formsemestre.id, + text=f"""Calcul jury automatique du semestre {formsemestre.html_link_status() + } ({nb_valid} décisions)""", + url=url_for( + "notes.formsemestre_status", + scodoc_dept=g.scodoc_dept, + formsemestre_id=formsemestre.id, + ), ) + H = [ + f"""{html_sco_header.sco_header(page_title="Saisie automatique")} + <h2>Saisie automatique des décisions du semestre {formsemestre.titre_annee()}</h2> + <p>Opération effectuée.</p> + <p>{nb_valid} étudiants validés sur {len(etudids)}</p> + """ + ] if conflicts: H.append( f"""<p><b>Attention:</b> {len(conflicts)} étudiants non modifiés diff --git a/app/scodoc/sco_import_etuds.py b/app/scodoc/sco_import_etuds.py index 736d6021210b25c524cb153cb8f58639beacd3bb..e7eb8dceb3c445c3d15c1d1975bd7e5cc7449ae6 100644 --- a/app/scodoc/sco_import_etuds.py +++ b/app/scodoc/sco_import_etuds.py @@ -480,6 +480,7 @@ def scolars_import_excel_file( text="Inscription de %d étudiants" # peuvent avoir ete inscrits a des semestres differents % len(created_etudids), obj=formsemestre_id, + max_frequency=0, ) log("scolars_import_excel_file: completing transaction") diff --git a/app/scodoc/sco_synchro_etuds.py b/app/scodoc/sco_synchro_etuds.py index 6685909c4ba47d6f7ba0198222781bf5d2db8d87..764d889f4234bb7ad4d334bc80ce3102569053fe 100644 --- a/app/scodoc/sco_synchro_etuds.py +++ b/app/scodoc/sco_synchro_etuds.py @@ -704,7 +704,6 @@ def do_import_etuds_from_portal(sem, a_importer, etudsapo_ident): typ=ScolarNews.NEWS_INSCR, text=f"Import Apogée de {len(created_etudids)} étudiants en ", obj=sem["formsemestre_id"], - max_frequency=10 * 60, # 10' ) diff --git a/app/static/css/scodoc.css b/app/static/css/scodoc.css index 0173a14e5ed3b6256f5ed14ef09d63ece2b88049..ca1b1f12907004167d1f78b0a2d8a22c235c1277 100644 --- a/app/static/css/scodoc.css +++ b/app/static/css/scodoc.css @@ -629,7 +629,7 @@ div.news { border-radius: 8px; } -div.news a { +div.news a, div.news a.stdlink { color: black; text-decoration: none; } diff --git a/app/views/notes.py b/app/views/notes.py index c51571941bebd3edc496eca1ac02f52c08e470d8..1ef706934a61784ea45bc9dd73bdad6c2c7f0069 100644 --- a/app/views/notes.py +++ b/app/views/notes.py @@ -2410,6 +2410,16 @@ def formsemestre_validation_but( if request.method == "POST": if not read_only: deca.record_form(request.form) + ScolarNews.add( + typ=ScolarNews.NEWS_JURY, + obj=formsemestre.id, + text=f"""Saisie décision jury dans {formsemestre.html_link_status()}""", + url=url_for( + "notes.formsemestre_status", + scodoc_dept=g.scodoc_dept, + formsemestre_id=formsemestre.id, + ), + ) flash("codes enregistrés") return flask.redirect( url_for( @@ -3059,7 +3069,6 @@ def formsemestre_set_apo_etapes(): ScolarNews.add( typ=ScolarNews.NEWS_APO, text=f"Modification code Apogée du semestre {formsemestre.titre_annee()})", - max_frequency=10 * 60, ) return ("", 204) @@ -3081,7 +3090,6 @@ def formsemestre_set_elt_annee_apo(): ScolarNews.add( typ=ScolarNews.NEWS_APO, text=f"Modification code Apogée du semestre {formsemestre.titre_annee()})", - max_frequency=10 * 60, ) return ("", 204) @@ -3103,7 +3111,6 @@ def formsemestre_set_elt_sem_apo(): ScolarNews.add( typ=ScolarNews.NEWS_APO, text=f"Modification code Apogée du semestre {formsemestre.titre_annee()})", - max_frequency=10 * 60, ) return ("", 204) @@ -3125,7 +3132,6 @@ def ue_set_apo(): ScolarNews.add( typ=ScolarNews.NEWS_FORM, text=f"Modification code Apogée d'UE dans la formation {ue.formation.titre} ({ue.formation.acronyme})", - max_frequency=10 * 60, ) return ("", 204) @@ -3146,8 +3152,8 @@ def module_set_apo(): db.session.commit() ScolarNews.add( typ=ScolarNews.NEWS_FORM, - text=f"Modification code Apogée d'UE dans la formation {mod.formation.titre} ({mod.formation.acronyme})", - max_frequency=10 * 60, + text=f"""Modification code Apogée d'UE dans la formation { + mod.formation.titre} ({mod.formation.acronyme})""", ) return ("", 204) diff --git a/sco_version.py b/sco_version.py index 7be8d568b31038b828eb31304fb44e65e3e8a0a3..bd238565a60cd07ecb8b1665a51263bbfd90ed81 100644 --- a/sco_version.py +++ b/sco_version.py @@ -1,7 +1,7 @@ # -*- mode: python -*- # -*- coding: utf-8 -*- -SCOVERSION = "9.4.90" +SCOVERSION = "9.4.91" SCONAME = "ScoDoc"