From fbca147d7e717eb985cf1a9f93efdfb61c10ec31 Mon Sep 17 00:00:00 2001 From: Emmanuel Viennet Date: Sun, 18 Jun 2023 09:37:13 +0200 Subject: [PATCH] =?UTF-8?q?Suppressions=20de=20d=C3=A9cisions=20de=20jury?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/but/jury_but.py | 9 +- app/but/jury_but_view.py | 2 +- app/comp/jury.py | 90 ++++++++++++++++++- app/models/but_validations.py | 9 +- app/models/formations.py | 2 +- app/models/formsemestre.py | 10 ++- app/models/validations.py | 33 ++++++- app/scodoc/sco_cache.py | 13 +++ app/scodoc/sco_edit_ue.py | 9 +- app/scodoc/sco_recapcomplet.py | 4 +- app/scodoc/sco_semset.py | 7 +- app/static/css/cursus_but.css | 7 +- app/templates/bul_head.j2 | 8 +- app/templates/but/cursus_etud.j2 | 6 +- app/templates/but/parcour_formation.j2 | 4 +- .../jury/erase_decisions_annee_formation.j2 | 41 +++++++++ app/views/notes.py | 71 ++++++++++++--- 17 files changed, 276 insertions(+), 49 deletions(-) create mode 100644 app/templates/jury/erase_decisions_annee_formation.j2 diff --git a/app/but/jury_but.py b/app/but/jury_but.py index c1ded7b8..af9b4673 100644 --- a/app/but/jury_but.py +++ b/app/but/jury_but.py @@ -75,11 +75,9 @@ from app.comp.res_but import ResultatsSemestreBUT from app.comp import res_sem from app.models.but_refcomp import ( - ApcAnneeParcours, ApcCompetence, ApcNiveau, ApcParcours, - ApcParcoursNiveauCompetence, ) from app.models import Scolog, ScolarAutorisationInscription from app.models.but_validations import ( @@ -89,7 +87,7 @@ from app.models.but_validations import ( ) from app.models.etudiants import Identite from app.models.formations import Formation -from app.models.formsemestre import FormSemestre, FormSemestreInscription +from app.models.formsemestre import FormSemestre from app.models.ues import UniteEns from app.models.validations import ScolarFormSemestreValidation from app.scodoc import sco_cache @@ -473,7 +471,7 @@ class DecisionsProposeesAnnee(DecisionsProposees): scodoc_dept=g.scodoc_dept, semestre_idx=formsemestre.semestre_id, formation_id=formsemestre.formation.id)}"> - {formsemestre.formation.to_html()} ({ + {formsemestre.formation.html()} ({ formsemestre.formation.id}) @@ -902,7 +900,8 @@ class DecisionsProposeesAnnee(DecisionsProposees): pour cette année: décisions d'UE, de RCUE, d'année, et autorisations d'inscription émises. Efface même si étudiant DEM ou DEF. - Si à cheval, n'efface que pour le semestre d'origine du deca. + Si à cheval ou only_one_sem, n'efface que les décisions UE et les + autorisations de passage du semestre d'origine du deca. (commite la session.) """ if only_one_sem or self.a_cheval: diff --git a/app/but/jury_but_view.py b/app/but/jury_but_view.py index 0a20e933..a61f1f14 100644 --- a/app/but/jury_but_view.py +++ b/app/but/jury_but_view.py @@ -246,7 +246,7 @@ def _gen_but_rcue(dec_rcue: DecisionsProposeesRCUE, niveau: ApcNiveau) -> str: scoplement = ( f"""
{ - dec_rcue.validation.to_html() + dec_rcue.validation.html() }
""" if dec_rcue.validation else "" diff --git a/app/comp/jury.py b/app/comp/jury.py index cee32ffd..1c43158d 100644 --- a/app/comp/jury.py +++ b/app/comp/jury.py @@ -10,8 +10,17 @@ import pandas as pd import sqlalchemy as sa from app import db -from app.models import FormSemestre, Identite, ScolarFormSemestreValidation, UniteEns from app.comp.res_cache import ResultatsCache +from app.models import ( + ApcValidationAnnee, + ApcValidationRCUE, + Formation, + FormSemestre, + Identite, + ScolarAutorisationInscription, + ScolarFormSemestreValidation, + UniteEns, +) from app.scodoc import sco_cache from app.scodoc import codes_cursus @@ -81,7 +90,7 @@ class ValidationsSemestre(ResultatsCache): # UEs: { etudid : { ue_id : {"code":, "ects":, "event_date":} }} decisions_jury_ues = {} - # Parcours les décisions d'UE: + # Parcoure les décisions d'UE: for decision in ( decisions_jury_q.filter(db.text("ue_id is not NULL")) .join(UniteEns) @@ -172,3 +181,80 @@ def formsemestre_get_ue_capitalisees(formsemestre: FormSemestre) -> pd.DataFrame with db.engine.begin() as connection: df = pd.read_sql_query(query, connection, params=params, index_col="etudid") return df + + +def erase_decisions_annee_formation( + etud: Identite, formation: Formation, annee: int, delete=False +) -> list: + """Efface toutes les décisions de jury de l'étudiant dans les formations de même code + que celle donnée pour cette année de la formation: + UEs, RCUEs de l'année BUT, année BUT, passage vers l'année suivante. + Ne considère pas l'origine de la décision. + annee: entier, 1, 2, 3, ... + Si delete est faux, renvoie la liste des validations qu'il faudrait effacer, sans y toucher. + """ + sem1, sem2 = annee * 2 - 1, annee * 2 + # UEs + validations = ( + ScolarFormSemestreValidation.query.filter_by(etudid=etud.id) + .join(UniteEns) + .filter(db.or_(UniteEns.semestre_idx == sem1, UniteEns.semestre_idx == sem2)) + .join(Formation) + .filter_by(formation_code=formation.formation_code) + .order_by( + UniteEns.acronyme, UniteEns.numero + ) # acronyme d'abord car 2 semestres + .all() + ) + # RCUEs (a priori inutile de matcher sur l'ue2_id) + validations += ( + ApcValidationRCUE.query.filter_by(etudid=etud.id) + .join(UniteEns, UniteEns.id == ApcValidationRCUE.ue1_id) + .filter_by(semestre_idx=sem1) + .join(Formation) + .filter_by(formation_code=formation.formation_code) + .order_by(UniteEns.acronyme, UniteEns.numero) + .all() + ) + # Validation de semestres classiques + validations += ( + ScolarFormSemestreValidation.query.filter_by(etudid=etud.id, ue_id=None) + .join( + FormSemestre, + FormSemestre.id == ScolarFormSemestreValidation.formsemestre_id, + ) + .filter( + db.or_(FormSemestre.semestre_id == sem1, FormSemestre.semestre_id == sem2) + ) + .join(Formation) + .filter_by(formation_code=formation.formation_code) + .all() + ) + # Année BUT + validations += ( + ApcValidationAnnee.query.filter_by(etudid=etud.id, ordre=annee) + .join(Formation) + .filter_by(formation_code=formation.formation_code) + .all() + ) + # Autorisations vers les semestres suivants ceux de l'année: + validations += ( + ScolarAutorisationInscription.query.filter_by( + etudid=etud.id, formation_code=formation.formation_code + ) + .filter( + db.or_( + ScolarAutorisationInscription.semestre_id == sem1 + 1, + ScolarAutorisationInscription.semestre_id == sem2 + 1, + ) + ) + .all() + ) + + if delete: + for validation in validations: + db.session.delete(validation) + db.session.commit() + sco_cache.invalidate_formsemestre_etud(etud) + return [] + return validations diff --git a/app/models/but_validations.py b/app/models/but_validations.py index c2be058b..77816476 100644 --- a/app/models/but_validations.py +++ b/app/models/but_validations.py @@ -66,7 +66,7 @@ class ApcValidationRCUE(db.Model): return f"""Décision sur RCUE {self.ue1.acronyme}/{self.ue2.acronyme}: { self.code} enregistrée le {self.date.strftime("%d/%m/%Y")}""" - def to_html(self) -> str: + def html(self) -> str: "description en HTML" return f"""Décision sur RCUE {self.ue1.acronyme}/{self.ue2.acronyme}: {self.code} @@ -348,6 +348,13 @@ class ApcValidationAnnee(db.Model): "ordre": self.ordre, } + def html(self) -> str: + "Affichage html" + return f"""Validation année BUT{self.ordre} émise par + {self.formsemestre.html_link_status() if self.formsemestre else "-"} + le {self.date.strftime("%d/%m/%Y")} à {self.date.strftime("%Hh%M")} + """ + def dict_decision_jury(etud: Identite, formsemestre: FormSemestre) -> dict: """ diff --git a/app/models/formations.py b/app/models/formations.py index e98d66f7..fb7529e3 100644 --- a/app/models/formations.py +++ b/app/models/formations.py @@ -60,7 +60,7 @@ class Formation(db.Model): return f"""<{self.__class__.__name__}(id={self.id}, dept_id={ self.dept_id}, acronyme={self.acronyme!r}, version={self.version})>""" - def to_html(self) -> str: + def html(self) -> str: "titre complet pour affichage" return f"""Formation {self.titre} ({self.acronyme}) [version {self.version}] code {self.formation_code}""" diff --git a/app/models/formsemestre.py b/app/models/formsemestre.py index 47957c84..d276f1eb 100644 --- a/app/models/formsemestre.py +++ b/app/models/formsemestre.py @@ -16,7 +16,7 @@ from operator import attrgetter from flask_login import current_user -from flask import flash, g +from flask import flash, g, url_for from sqlalchemy.sql import text import app.scodoc.sco_utils as scu @@ -165,6 +165,14 @@ class FormSemestre(db.Model): def __repr__(self): return f"<{self.__class__.__name__} {self.id} {self.titre_annee()}>" + def html_link_status(self) -> str: + "html link to status page" + return f"""{self.titre_mois()} + """ + @classmethod def get_formsemestre(cls, formsemestre_id: int) -> "FormSemestre": """ "FormSemestre ou 404, cherche uniquement dans le département courant""" diff --git a/app/models/validations.py b/app/models/validations.py index 229d15ad..9a938b6c 100644 --- a/app/models/validations.py +++ b/app/models/validations.py @@ -59,13 +59,16 @@ class ScolarFormSemestreValidation(db.Model): ) def __repr__(self): - return f"{self.__class__.__name__}(sem={self.formsemestre_id}, etuid={self.etudid}, code={self.code}, ue={self.ue}, moy_ue={self.moy_ue})" + return f"""{self.__class__.__name__}(sem={self.formsemestre_id}, etuid={ + self.etudid}, code={self.code}, ue={self.ue}, moy_ue={self.moy_ue})""" def __str__(self): if self.ue_id: # Note: si l'objet vient d'être créé, ue_id peut exister mais pas ue ! - return f"""décision sur UE {self.ue.acronyme if self.ue else self.ue_id}: {self.code}""" - return f"""décision sur semestre {self.formsemestre.titre_mois()} du {self.event_date.strftime("%d/%m/%Y")}""" + return f"""décision sur UE {self.ue.acronyme if self.ue else self.ue_id + }: {self.code}""" + return f"""décision sur semestre {self.formsemestre.titre_mois()} du { + self.event_date.strftime("%d/%m/%Y")}""" def to_dict(self) -> dict: "as a dict" @@ -73,6 +76,20 @@ class ScolarFormSemestreValidation(db.Model): d.pop("_sa_instance_state", None) return d + def html(self) -> str: + "Affichage html" + if self.ue_id is not None: + return f"""Validation de l'UE {self.ue.acronyme} + ({self.code} + le {self.event_date.strftime("%d/%m/%Y")} à {self.event_date.strftime("%Hh%M")}) + """ + else: + return f"""Validation du semestre S{ + self.formsemestre.semestre_id if self.formsemestre else "?"} + ({self.code} + le {self.event_date.strftime("%d/%m/%Y")} à {self.event_date.strftime("%Hh%M")}) + """ + class ScolarAutorisationInscription(db.Model): """Autorisation d'inscription dans un semestre""" @@ -93,6 +110,7 @@ class ScolarAutorisationInscription(db.Model): db.Integer, db.ForeignKey("notes_formsemestre.id"), ) + origin_formsemestre = db.relationship("FormSemestre", lazy="select", uselist=False) def __repr__(self) -> str: return f"""{self.__class__.__name__}(id={self.id}, etudid={ @@ -104,6 +122,15 @@ class ScolarAutorisationInscription(db.Model): d.pop("_sa_instance_state", None) return d + def html(self) -> str: + "Affichage html" + return f"""Autorisation de passage vers S{self.semestre_id} émise par + {self.origin_formsemestre.html_link_status() + if self.origin_formsemestre + else "-"} + le {self.date.strftime("%d/%m/%Y")} à {self.date.strftime("%Hh%M")} + """ + @classmethod def autorise_etud( cls, diff --git a/app/scodoc/sco_cache.py b/app/scodoc/sco_cache.py index 0b9d27a8..36b8db30 100644 --- a/app/scodoc/sco_cache.py +++ b/app/scodoc/sco_cache.py @@ -315,6 +315,19 @@ def invalidate_formsemestre( # was inval_cache(formsemestre_id=None, pdfonly=Fa SemBulletinsPDFCache.invalidate_sems(formsemestre_ids) +def invalidate_formsemestre_etud(etud: "Identite"): + """Invalide tous les formsemestres auxquels l'étudiant est inscrit""" + from app.models import FormSemestre, FormSemestreInscription + + inscriptions = ( + FormSemestreInscription.query.filter_by(etudid=etud.id) + .join(FormSemestre) + .filter_by(dept_id=g.scodoc_dept_id) + ) + for inscription in inscriptions: + invalidate_formsemestre(inscription.formsemestre_id) + + class DeferredSemCacheManager: """Contexte pour effectuer des opérations indépendantes dans la même requete qui invalident le cache. Par exemple, quand on inscrit diff --git a/app/scodoc/sco_edit_ue.py b/app/scodoc/sco_edit_ue.py index f41804f1..ba6cb918 100644 --- a/app/scodoc/sco_edit_ue.py +++ b/app/scodoc/sco_edit_ue.py @@ -757,7 +757,7 @@ def ue_table(formation_id=None, semestre_idx=1, msg=""): # was ue_list ], page_title=f"Programme {formation.acronyme} v{formation.version}", ), - f"""

{formation.to_html()} {lockicon} + f"""

{formation.html()} {lockicon}

""", ] @@ -1010,12 +1010,7 @@ du programme" (menu "Semestre") si vous avez un semestre en cours);