Update opolka/ScoDoc from ScoDoc/ScoDoc #2

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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>
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>%d étudiants validés (sur %s)</p>"""
% (sem["titreannee"], nb_valid, len(etudids))
)
<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

View File

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

View File

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

View File

@ -629,7 +629,7 @@ div.news {
border-radius: 8px;
}
div.news a {
div.news a, div.news a.stdlink {
color: black;
text-decoration: none;
}

View File

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

View File

@ -1,7 +1,7 @@
# -*- mode: python -*-
# -*- coding: utf-8 -*-
SCOVERSION = "9.4.90"
SCOVERSION = "9.4.91"
SCONAME = "ScoDoc"