From 1dfba157c28376195656af770b9bbbc8e286edef Mon Sep 17 00:00:00 2001 From: ilona Date: Fri, 11 Oct 2024 14:06:02 +0200 Subject: [PATCH] =?UTF-8?q?Backend=20'Matieres':=20utilise=20uniquement=20?= =?UTF-8?q?mod=C3=A8le.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/formations/edit_matiere.py | 202 +++++++-------------------------- app/formations/edit_module.py | 21 ++-- app/formations/edit_ue.py | 125 ++++++++++---------- app/formations/formation_io.py | 63 +++++----- app/models/formations.py | 81 ++++++++++++- app/models/modules.py | 49 +++++++- app/pe/moys/pe_ressemtag.py | 10 +- app/scodoc/sco_tag_module.py | 63 +--------- app/scodoc/sco_ue_external.py | 8 +- app/views/notes.py | 15 ++- tests/unit/sco_fake_gen.py | 7 +- tests/unit/test_formations.py | 13 +-- 12 files changed, 300 insertions(+), 357 deletions(-) diff --git a/app/formations/edit_matiere.py b/app/formations/edit_matiere.py index 26add3956..d758ee195 100644 --- a/app/formations/edit_matiere.py +++ b/app/formations/edit_matiere.py @@ -25,16 +25,14 @@ # ############################################################################## -"""Ajout/Modification/Supression matieres -(portage from DTML) +"""Ajout/Modification/Suppression matieres """ import flask -from flask import g, render_template, request, url_for +from flask import flash, g, render_template, request, url_for from app import db, log -from app.models import Formation, Matiere, UniteEns, ScolarNews +from app.models import Matiere, UniteEns -import app.scodoc.notesdb as ndb import app.scodoc.sco_utils as scu from app.scodoc.TrivialFormulator import TrivialFormulator, tf_error_message @@ -44,59 +42,9 @@ from app.scodoc.sco_exceptions import ( ScoNonEmptyFormationObject, ) -_matiereEditor = ndb.EditableTable( - "notes_matieres", - "matiere_id", - ("matiere_id", "ue_id", "numero", "titre"), - sortkey="numero", - output_formators={"numero": ndb.int_null_is_zero}, -) - - -def matiere_list(*args, **kw): - "list matieres" - cnx = ndb.GetDBConnexion() - return _matiereEditor.list(cnx, *args, **kw) - - -def do_matiere_edit(*args, **kw): - "edit a matiere" - from app.formations import edit_ue - - cnx = ndb.GetDBConnexion() - # check - mat = matiere_list({"matiere_id": args[0]["matiere_id"]})[0] - if matiere_is_locked(mat["matiere_id"]): - raise ScoLockedFormError() - # edit - _matiereEditor.edit(cnx, *args, **kw) - formation_id = edit_ue.ue_list({"ue_id": mat["ue_id"]})[0]["formation_id"] - db.session.get(Formation, formation_id).invalidate_cached_sems() - - -def do_matiere_create(args): - "create a matiere" - from app.formations import edit_ue - - cnx = ndb.GetDBConnexion() - # check - ue = edit_ue.ue_list({"ue_id": args["ue_id"]})[0] - # create matiere - r = _matiereEditor.create(cnx, args) - - # news - formation = db.session.get(Formation, ue["formation_id"]) - ScolarNews.add( - typ=ScolarNews.NEWS_FORM, - obj=ue["formation_id"], - text=f"Modification de la formation {formation.acronyme}", - ) - formation.invalidate_cached_sems() - return r - def matiere_create(ue_id=None): - """Creation d'une matiere""" + """Formulaire création d'une matiere""" ue: UniteEns = UniteEns.query.get_or_404(ue_id) default_numero = max([mat.numero for mat in ue.matieres] or [9]) + 1 H = [ @@ -153,8 +101,8 @@ associé. if tf[0] == -1: return flask.redirect(dest_url) # check unicity - mats = matiere_list(args={"ue_id": ue_id, "titre": tf[2]["titre"]}) - if mats: + nb_mats = Matiere.query.filter_by(ue_id=ue_id, titre=tf[2]["titre"]).count() + if nb_mats: return render_template( "sco_page.j2", title="Création d'une matière", @@ -164,56 +112,14 @@ associé. + tf[1] ), ) - _ = do_matiere_create(tf[2]) + Matiere.create_from_dict(tf[2]) return flask.redirect(dest_url) -def can_delete_matiere(matiere: Matiere) -> tuple[bool, str]: - "True si la matiere n'est pas utilisée dans des formsemestre" - locked = matiere_is_locked(matiere.id) - if locked: - return False - if any(m.modimpls.all() for m in matiere.modules): - return False - return True - - -def do_matiere_delete(oid): - "delete matiere and attached modules" - from app.formations import edit_module, edit_ue - - cnx = ndb.GetDBConnexion() - # check - matiere = Matiere.query.get_or_404(oid) - mat = matiere_list({"matiere_id": oid})[0] # compat sco7 - ue = edit_ue.ue_list({"ue_id": mat["ue_id"]})[0] - if not can_delete_matiere(matiere): - # il y a au moins un modimpl dans un module de cette matière - raise ScoNonEmptyFormationObject("Matière", matiere.titre) - - log(f"do_matiere_delete: matiere_id={matiere.id}") - # delete all modules in this matiere - mods = edit_module.module_list({"matiere_id": matiere.id}) - for mod in mods: - edit_module.do_module_delete(mod["module_id"]) - _matiereEditor.delete(cnx, oid) - - # news - formation = db.session.get(Formation, ue["formation_id"]) - ScolarNews.add( - typ=ScolarNews.NEWS_FORM, - obj=ue["formation_id"], - text=f"Modification de la formation {formation.acronyme}", - ) - formation.invalidate_cached_sems() - - def matiere_delete(matiere_id=None): - """Delete matière""" - from app.formations import edit_ue - - matiere = Matiere.query.get_or_404(matiere_id) - if not can_delete_matiere(matiere): + """Form delete matière""" + matiere = Matiere.get_instance(matiere_id) + if not matiere.can_be_deleted(): # il y a au moins un modimpl dans un module de cette matière raise ScoNonEmptyFormationObject( "Matière", @@ -226,22 +132,20 @@ def matiere_delete(matiere_id=None): ), ) - mat = matiere_list(args={"matiere_id": matiere_id})[0] - ue_dict = edit_ue.ue_list(args={"ue_id": mat["ue_id"]})[0] H = [ - "

Suppression de la matière %(titre)s" % mat, - " dans l'UE (%(acronyme)s))

" % ue_dict, + f"""

Suppression de la matière {matiere.titre} + dans l'UE {matiere.ue.acronyme}

""", ] dest_url = url_for( "notes.ue_table", scodoc_dept=g.scodoc_dept, - formation_id=str(ue_dict["formation_id"]), + formation_id=matiere.ue.formation_id, ) tf = TrivialFormulator( request.base_url, scu.get_request_args(), (("matiere_id", {"input_type": "hidden"}),), - initvalues=mat, + initvalues=matiere.to_dict(), submitlabel="Confirmer la suppression", cancelbutton="Annuler", ) @@ -254,29 +158,23 @@ def matiere_delete(matiere_id=None): if tf[0] == -1: return flask.redirect(dest_url) - do_matiere_delete(matiere_id) + matiere.delete() return flask.redirect(dest_url) def matiere_edit(matiere_id=None): - """Edit matiere""" - from app.formations import edit_ue - - F = matiere_list(args={"matiere_id": matiere_id}) - if not F: - raise ScoValueError("Matière inexistante !") - F = F[0] - ues = edit_ue.ue_list(args={"ue_id": F["ue_id"]}) - if not ues: - raise ScoValueError("UE inexistante !") - ue = ues[0] - formation: Formation = Formation.query.get_or_404(ue["formation_id"]) - ues = edit_ue.ue_list(args={"formation_id": ue["formation_id"]}) - ue_names = ["%(acronyme)s (%(titre)s)" % u for u in ues] - ue_ids = [u["ue_id"] for u in ues] + """Form edit matiere""" + matiere: Matiere = Matiere.get_instance(matiere_id) + if matiere.is_locked(): + raise ScoLockedFormError() + ue = matiere.ue + formation = ue.formation + ues = matiere.ue.formation.ues + ue_names = [f"{u.acronyme} ({u.titre or ''})" for u in ues] + ue_ids = [u.id for u in ues] H = [ - """

Modification de la matière %(titre)s""" % F, - f"""(formation ({formation.acronyme}, version {formation.version})

""", + f"""

Modification de la matière {matiere.titre or 'sans titre'} + (formation ({formation.acronyme}, version {formation.version})

""", ] help_msg = """

Les matières sont des groupes de modules dans une UE d'une formation donnée. Les matières servent surtout pour la @@ -316,14 +214,14 @@ associé. }, ), ), - initvalues=F, + initvalues=matiere.to_dict(), submitlabel="Modifier les valeurs", ) dest_url = url_for( "notes.ue_table", scodoc_dept=g.scodoc_dept, - formation_id=str(ue["formation_id"]), + formation_id=formation.id, ) if tf[0] == 0: return render_template( @@ -335,8 +233,8 @@ associé. return flask.redirect(dest_url) else: # check unicity - mats = matiere_list(args={"ue_id": tf[2]["ue_id"], "titre": tf[2]["titre"]}) - if len(mats) > 1 or (len(mats) == 1 and mats[0]["matiere_id"] != matiere_id): + mats = Matiere.query.filter_by(ue_id=tf[2]["ue_id"], titre=tf[2]["titre"]).all() + if len(mats) > 1 or (len(mats) == 1 and mats[0].id != matiere_id): return render_template( "sco_page.j2", title="Modification d'une matière", @@ -347,32 +245,18 @@ associé. ), ) + modif = False # changement d'UE ? - if tf[2]["ue_id"] != F["ue_id"]: - log("attaching mat %s to new UE %s" % (matiere_id, tf[2]["ue_id"])) - ndb.SimpleQuery( - "UPDATE notes_modules SET ue_id = %(ue_id)s WHERE matiere_id=%(matiere_id)s", - {"ue_id": tf[2]["ue_id"], "matiere_id": matiere_id}, - ) - - do_matiere_edit(tf[2]) - + if tf[2]["ue_id"] != ue.id: + log(f"attaching mat {matiere_id} to new UE id={tf[2]['ue_id']}") + new_ue = UniteEns.get_ue(tf[2]["ue_id"]) + if new_ue.formation_id != formation.id: + raise ScoValueError("UE does not belong to the same formation") + matiere.ue = new_ue + modif = True + modif |= matiere.from_dict(tf[2]) + if modif: + db.session.commit() + matiere.ue.formation.invalidate_cached_sems() + flash("Matière modifiée", "info") return flask.redirect(dest_url) - - -def matiere_is_locked(matiere_id): - """True if matiere should not be modified - (contains modules used in a locked formsemestre) - """ - r = ndb.SimpleDictFetch( - """SELECT ma.id - FROM notes_matieres ma, notes_modules mod, notes_formsemestre sem, notes_moduleimpl mi - WHERE ma.id = mod.matiere_id - AND mi.module_id = mod.id - AND mi.formsemestre_id = sem.id - AND ma.id = %(matiere_id)s - AND sem.etat = false - """, - {"matiere_id": matiere_id}, - ) - return len(r) > 0 diff --git a/app/formations/edit_module.py b/app/formations/edit_module.py index 8cb8dccf6..71fd3ade7 100644 --- a/app/formations/edit_module.py +++ b/app/formations/edit_module.py @@ -767,6 +767,7 @@ def module_edit( module_dict["semestre_id"] = 1 else: module_dict["semestre_id"] = module.ue.semestre_idx + tags = module.tags if module else [] tf = TrivialFormulator( request.base_url, scu.get_request_args(), @@ -774,7 +775,9 @@ def module_edit( html_foot_markup=( f"""

+ >{ + ','.join(t.title for t in tags) + } """ if not create else "" @@ -833,10 +836,14 @@ def module_edit( if matiere: tf[2]["matiere_id"] = matiere.id else: - matiere_id = edit_matiere.do_matiere_create( - {"ue_id": ue.id, "titre": ue.titre or "", "numero": 1}, + matiere = Matiere.create_from_dict( + { + "ue_id": ue.id, + "titre": ue.titre or "", + "numero": 1, + } ) - tf[2]["matiere_id"] = matiere_id + tf[2]["matiere_id"] = matiere.id tf[2]["semestre_id"] = ue.semestre_idx module_id = do_module_create(tf[2]) @@ -946,12 +953,6 @@ def module_is_locked(module_id): return len(r) > 0 -def module_count_moduleimpls(module_id): - "Number of moduleimpls using this module" - mods = sco_moduleimpl.moduleimpl_list(module_id=module_id) - return len(mods) - - def formation_add_malus_modules( formation_id: int, semestre_id: int = None, titre=None, redirect=True ): diff --git a/app/formations/edit_ue.py b/app/formations/edit_ue.py index 3e4d13641..dbf2a1dbd 100644 --- a/app/formations/edit_ue.py +++ b/app/formations/edit_ue.py @@ -547,9 +547,10 @@ def ue_edit(ue_id=None, create=False, formation_id=None, default_semestre_idx=No if is_apc or cursus.UE_IS_MODULE or tf[2]["create_matiere"]: # rappel: en APC, toutes les UE ont une matière, créée ici # (inutilisée mais à laquelle les modules sont rattachés) - matiere_id = edit_matiere.do_matiere_create( - {"ue_id": ue_id, "titre": tf[2]["titre"], "numero": 1}, + matiere = Matiere.create_from_dict( + {"ue_id": ue_id, "titre": tf[2]["titre"], "numero": 1} ) + matiere_id = matiere.id if cursus.UE_IS_MODULE: # dans ce mode, crée un (unique) module dans l'UE: _ = edit_module.do_module_create( @@ -676,9 +677,10 @@ def ue_delete(ue_id=None, delete_validations=False, dialog_confirmed=False): return do_ue_delete(ue, delete_validations=delete_validations) -def ue_table(formation_id=None, semestre_idx=1, msg=""): # was ue_list - """Liste des matières et modules d'une formation, avec liens pour - éditer (si non verrouillée). +def ue_table(formation_id=None, semestre_idx=1, msg=""): + """Page affiochage ou édition d'une formation + avec UEs, matières et module, + et liens pour éditer si non verrouillée et permission. """ from app.scodoc import sco_formsemestre_validation @@ -1240,7 +1242,7 @@ def _ue_table_ues( def _ue_table_matieres( parcours, - ue, + ue_dict: dict, editable, tag_editable, arrow_up, @@ -1250,26 +1252,27 @@ def _ue_table_matieres( delete_disabled_icon, ): """Édition de programme: liste des matières (et leurs modules) d'une UE.""" + ue = UniteEns.get_ue(ue_dict["ue_id"]) H = [] if not parcours.UE_IS_MODULE: H.append('") @@ -1307,9 +1313,9 @@ def _ue_table_matieres( def _ue_table_modules( parcours, - ue, - mat, - modules, + ue: UniteEns, + mat: Matiere, + modules: list[Module], editable, tag_editable, arrow_up, @@ -1326,89 +1332,84 @@ def _ue_table_modules( H = ['