Modernisation suppressions UE et formations

This commit is contained in:
Emmanuel Viennet 2022-07-13 18:52:07 +02:00
parent 9480e15b83
commit 48e31b5f39
7 changed files with 88 additions and 64 deletions

View File

@ -105,9 +105,11 @@ class Formation(db.Model):
return len(self.formsemestres.filter_by(etat=False).all()) > 0 return len(self.formsemestres.filter_by(etat=False).all()) > 0
def invalidate_module_coefs(self, semestre_idx: int = None): def invalidate_module_coefs(self, semestre_idx: int = None):
"""Invalide les coefficients de modules cachés. """Invalide le cache des coefficients de modules.
Si semestre_idx est None, invalide tous les semestres, Si semestre_idx est None, invalide les coefs de tous les semestres,
sinon invalide le semestre indiqué et le cache de la formation. sinon invalide le semestre indiqué et le cache de la formation.
Dans tous les cas, invalide tous les formsemestres.
""" """
if semestre_idx is None: if semestre_idx is None:
keys = {f"{self.id}.{m.semestre_id}" for m in self.modules} keys = {f"{self.id}.{m.semestre_id}" for m in self.modules}

View File

@ -83,10 +83,9 @@ class UniteEns(db.Model):
return sco_edit_ue.ue_is_locked(self.id) return sco_edit_ue.ue_is_locked(self.id)
def can_be_deleted(self) -> bool: def can_be_deleted(self) -> bool:
"""True si l'UE n'est pas utilisée dans des formsemestre """True si l'UE n'a pas de moduleimpl rattachés
et n'a pas de module rattachés (pas un seul module de cette UE n'a de modimpl)
""" """
# "pas un seul module de cette UE n'a de modimpl...""
return (self.modules.count() == 0) or not any( return (self.modules.count() == 0) or not any(
m.modimpls.all() for m in self.modules m.modimpls.all() for m in self.modules
) )

View File

@ -228,7 +228,7 @@ class TableRecapWithEvalsCache(ScoDocCache):
def invalidate_formsemestre( # was inval_cache(formsemestre_id=None, pdfonly=False) def invalidate_formsemestre( # was inval_cache(formsemestre_id=None, pdfonly=False)
formsemestre_id=None, pdfonly=False formsemestre_id=None, pdfonly=False
): ):
"""expire cache pour un semestre (ou tous si formsemestre_id non spécifié). """expire cache pour un semestre (ou tous ceux du département si formsemestre_id non spécifié).
Si pdfonly, n'expire que les bulletins pdf cachés. Si pdfonly, n'expire que les bulletins pdf cachés.
""" """
from app.models.formsemestre import FormSemestre from app.models.formsemestre import FormSemestre

View File

@ -42,7 +42,7 @@ from app.models import ScolarNews
import app.scodoc.notesdb as ndb import app.scodoc.notesdb as ndb
import app.scodoc.sco_utils as scu import app.scodoc.sco_utils as scu
from app.scodoc.TrivialFormulator import TrivialFormulator, tf_error_message from app.scodoc.TrivialFormulator import TrivialFormulator, tf_error_message
from app.scodoc.sco_exceptions import ScoValueError, ScoLockedFormError from app.scodoc.sco_exceptions import ScoValueError, ScoNonEmptyFormationObject
from app.scodoc import html_sco_header from app.scodoc import html_sco_header
from app.scodoc import sco_codes_parcours from app.scodoc import sco_codes_parcours
@ -100,26 +100,38 @@ def formation_delete(formation_id=None, dialog_confirmed=False):
return "\n".join(H) return "\n".join(H)
def do_formation_delete(oid): def do_formation_delete(formation_id):
"""delete a formation (and all its UE, matieres, modules) """delete a formation (and all its UE, matieres, modules)
XXX delete all ues, will break if there are validations ! USE WITH CARE ! Warning: delete all ues, will ask if there are validations !
""" """
F = sco_formations.formation_list(args={"formation_id": oid})[0] formation: Formation = Formation.query.get(formation_id)
if sco_formations.formation_has_locked_sems(oid): if formation is None:
raise ScoLockedFormError() return
cnx = ndb.GetDBConnexion() acronyme = formation.acronyme
# delete all UE in this formation if formation.formsemestres.count():
ues = sco_edit_ue.ue_list({"formation_id": oid}) raise ScoNonEmptyFormationObject(
for ue in ues: type_objet="formation",
sco_edit_ue.do_ue_delete(ue["ue_id"], force=True) msg=formation.titre,
dest_url=url_for(
"notes.ue_table", scodoc_dept=g.scodoc_dept, formation_id=formation.id
),
)
sco_formations._formationEditor.delete(cnx, oid) # Suppression des modules
for module in formation.modules:
db.session.delete(module)
db.session.flush()
# Suppression des UEs
for ue in formation.ues:
sco_edit_ue.do_ue_delete(ue, force=True)
db.session.delete(formation)
# news # news
ScolarNews.add( ScolarNews.add(
typ=ScolarNews.NEWS_FORM, typ=ScolarNews.NEWS_FORM,
obj=oid, obj=formation_id,
text=f"Suppression de la formation {F['acronyme']}", text=f"Suppression de la formation {acronyme}",
) )

View File

@ -37,7 +37,14 @@ from app import db
from app import log from app import log
from app.but import apc_edit_ue from app.but import apc_edit_ue
from app.models import APO_CODE_STR_LEN, SHORT_STR_LEN from app.models import APO_CODE_STR_LEN, SHORT_STR_LEN
from app.models import Formation, UniteEns, ModuleImpl, Module from app.models import (
Formation,
FormSemestreUEComputationExpr,
FormSemestreUECoef,
Matiere,
UniteEns,
)
from app.models import ApcValidationRCUE, ScolarFormSemestreValidation, ScolarEvent
from app.models import ScolarNews from app.models import ScolarNews
from app.models.formations import Matiere from app.models.formations import Matiere
import app.scodoc.notesdb as ndb import app.scodoc.notesdb as ndb
@ -138,12 +145,11 @@ def do_ue_create(args):
return ue_id return ue_id
def do_ue_delete(ue_id, delete_validations=False, force=False): def do_ue_delete(ue: UniteEns, delete_validations=False, force=False):
"delete UE and attached matieres (but not modules)" """delete UE and attached matieres (but not modules).
from app.scodoc import sco_cursus_dut Si force, pas de confirmation dialog et pas de redirect
"""
ue = UniteEns.query.get_or_404(ue_id) formation: Formation = ue.formation
formation = ue.formation
semestre_idx = ue.semestre_idx semestre_idx = ue.semestre_idx
if not ue.can_be_deleted(): if not ue.can_be_deleted():
raise ScoNonEmptyFormationObject( raise ScoNonEmptyFormationObject(
@ -157,20 +163,22 @@ def do_ue_delete(ue_id, delete_validations=False, force=False):
), ),
) )
cnx = ndb.GetDBConnexion() log(f"do_ue_delete: ue_id={ue.id}, delete_validations={delete_validations}")
log("do_ue_delete: ue_id=%s, delete_validations=%s" % (ue.id, delete_validations))
# check
# if ue_is_locked(ue.id):
# raise ScoLockedFormError()
# Il y a-t-il des etudiants ayant validé cette UE ? # Il y a-t-il des etudiants ayant validé cette UE ?
# si oui, propose de supprimer les validations # si oui, propose de supprimer les validations
validations = sco_cursus_dut.scolar_formsemestre_validation_list( validations_ue = ScolarFormSemestreValidation.query.filter_by(ue_id=ue.id).all()
cnx, args={"ue_id": ue.id} validations_rcue = ApcValidationRCUE.query.filter(
) (ApcValidationRCUE.ue1_id == ue.id) | (ApcValidationRCUE.ue2_id == ue.id)
if validations and not delete_validations and not force: ).all()
if (
(len(validations_ue) > 0 or len(validations_rcue) > 0)
and not delete_validations
and not force
):
return scu.confirm_dialog( return scu.confirm_dialog(
"<p>%d étudiants ont validé l'UE %s (%s)</p><p>Si vous supprimez cette UE, ces validations vont être supprimées !</p>" f"""<p>Des étudiants ont une décision de jury sur l'UE {ue.acronyme} ({ue.titre})</p>
% (len(validations), ue.acronyme, ue.titre), <p>Si vous supprimez cette UE, ces décisions vont être supprimées !</p>""",
dest_url="", dest_url="",
target_variable="delete_validations", target_variable="delete_validations",
cancel_url=url_for( cancel_url=url_for(
@ -183,31 +191,34 @@ def do_ue_delete(ue_id, delete_validations=False, force=False):
) )
if delete_validations: if delete_validations:
log(f"deleting all validations of UE {ue.id}") log(f"deleting all validations of UE {ue.id}")
ndb.SimpleQuery( for v in validations_ue:
"DELETE FROM scolar_formsemestre_validation WHERE ue_id=%(ue_id)s", db.session.delete(v)
{"ue_id": ue.id}, for v in validations_rcue:
) db.session.delete(v)
# delete old formulas # delete old formulas
ndb.SimpleQuery( formulas = FormSemestreUEComputationExpr.query.filter_by(ue_id=ue.id).all()
"DELETE FROM notes_formsemestre_ue_computation_expr WHERE ue_id=%(ue_id)s", for formula in formulas:
{"ue_id": ue.id}, db.session.delete(formula)
)
# delete all matiere in this UE # delete all matieres in this UE
mats = sco_edit_matiere.matiere_list({"ue_id": ue.id}) for mat in Matiere.query.filter_by(ue_id=ue.id):
for mat in mats: db.session.delete(mat)
sco_edit_matiere.do_matiere_delete(mat["matiere_id"])
# delete uecoef and events # delete uecoefs
ndb.SimpleQuery( for uecoef in FormSemestreUECoef.query.filter_by(ue_id=ue.id):
"DELETE FROM notes_formsemestre_uecoef WHERE ue_id=%(ue_id)s", db.session.delete(uecoef)
{"ue_id": ue.id}, # delete events
) for event in ScolarEvent.query.filter_by(ue_id=ue.id):
ndb.SimpleQuery("DELETE FROM scolar_events WHERE ue_id=%(ue_id)s", {"ue_id": ue.id}) db.session.delete(event)
cnx = ndb.GetDBConnexion() db.session.flush()
_ueEditor.delete(cnx, ue.id)
# > UE delete + supr. validations associées etudiants (cas compliqué, mais rarement db.session.delete(ue)
# utilisé: acceptable de tout invalider): db.session.commit()
# cas compliqué, mais rarement utilisé: acceptable de tout invalider
formation.invalidate_module_coefs() formation.invalidate_module_coefs()
# -> invalide aussi .invalidate_formsemestre() # -> invalide aussi les formsemestres
# news # news
ScolarNews.add( ScolarNews.add(
typ=ScolarNews.NEWS_FORM, typ=ScolarNews.NEWS_FORM,
@ -601,7 +612,7 @@ def ue_delete(ue_id=None, delete_validations=False, dialog_confirmed=False):
), ),
) )
return do_ue_delete(ue.id, delete_validations=delete_validations) return do_ue_delete(ue, delete_validations=delete_validations)
def ue_table(formation_id=None, semestre_idx=1, msg=""): # was ue_list def ue_table(formation_id=None, semestre_idx=1, msg=""): # was ue_list

View File

@ -116,7 +116,7 @@ class ScoNonEmptyFormationObject(ScoValueError):
"""On ne peut pas supprimer un module/matiere ou UE si des formsemestre s'y réfèrent""" """On ne peut pas supprimer un module/matiere ou UE si des formsemestre s'y réfèrent"""
def __init__(self, type_objet="objet'", msg="", dest_url=None): def __init__(self, type_objet="objet'", msg="", dest_url=None):
msg = f"""<h3>{type_objet} "{msg}" utilisé dans des semestres: suppression impossible.</h3> msg = f"""<h3>{type_objet} "{msg}" utilisé(e) dans des semestres: suppression impossible.</h3>
<p class="help">Il faut d'abord supprimer le semestre (ou en retirer ce {type_objet}). <p class="help">Il faut d'abord supprimer le semestre (ou en retirer ce {type_objet}).
Mais il est peut-être préférable de laisser ce programme intact et d'en créer une Mais il est peut-être préférable de laisser ce programme intact et d'en créer une
nouvelle version pour la modifier sans affecter les semestres déjà en place. nouvelle version pour la modifier sans affecter les semestres déjà en place.

View File

@ -327,7 +327,7 @@ def test_formations(test_client):
# --- Suppression d'une formation # --- Suppression d'une formation
sco_edit_formation.do_formation_delete(oid=formation_id2) sco_edit_formation.do_formation_delete(formation_id=formation_id2)
lif3 = notes.formation_list(format="json").get_data(as_text=True) lif3 = notes.formation_list(format="json").get_data(as_text=True)
assert isinstance(lif3, str) assert isinstance(lif3, str)
load_lif3 = json.loads(lif3) load_lif3 = json.loads(lif3)