diff --git a/app/formations/edit_ue.py b/app/formations/edit_ue.py index 1b9e3e334..483d9e1fa 100644 --- a/app/formations/edit_ue.py +++ b/app/formations/edit_ue.py @@ -49,7 +49,6 @@ from app.models import ( ) from app.models import ApcValidationRCUE, ScolarFormSemestreValidation, ScolarEvent from app.models import ScolarNews -import app.scodoc.notesdb as ndb import app.scodoc.sco_utils as scu from app.scodoc.sco_utils import ModuleType from app.scodoc.TrivialFormulator import TrivialFormulator @@ -65,54 +64,13 @@ from app.scodoc import sco_edit_apc from app.scodoc import sco_groups from app.scodoc import sco_moduleimpl -_ueEditor = ndb.EditableTable( - "notes_ue", - "ue_id", - ( - "ue_id", - "formation_id", - "acronyme", - "numero", - "titre", - "semestre_idx", - "type", - "ue_code", - "ects", - "is_external", - "code_apogee", - "code_apogee_rcue", - "coefficient", - "coef_rcue", - "color", - "niveau_competence_id", - ), - convert_empty_to_nulls=False, # necessaire pour ue_code == "" - sortkey="numero", - input_formators={ - "type": ndb.int_null_is_zero, - "is_external": scu.to_bool, - "ects": ndb.float_null_is_null, - }, - output_formators={ - "numero": ndb.int_null_is_zero, - "ects": ndb.float_null_is_null, - "coefficient": ndb.float_null_is_zero, - "semestre_idx": ndb.int_null_is_null, - }, -) - -def ue_list(*args, **kw): - "list UEs" - cnx = ndb.GetDBConnexion() - return _ueEditor.list(cnx, *args, **kw) - - -def do_ue_create(args, allow_empty_ue_code=False): +def do_ue_create(args, allow_empty_ue_code=False) -> UniteEns: "create an ue" - cnx = ndb.GetDBConnexion() # check duplicates - ues = ue_list({"formation_id": args["formation_id"], "acronyme": args["acronyme"]}) + ues = UniteEns.query.filter_by( + formation_id=args["formation_id"], acronyme=args["acronyme"] + ).all() if ues: raise ScoValueError( f"""Acronyme d'UE "{args['acronyme']}" déjà utilisé ! @@ -138,21 +96,21 @@ def do_ue_create(args, allow_empty_ue_code=False): args["coefficient"] = None # create - # XXX TODO utiliser UniteEns.create_from_dict - ue_id = _ueEditor.create(cnx, args) - log(f"do_ue_create: created {ue_id} with {args}") + ue = UniteEns.create_from_dict(args) + db.session.commit() + log(f"do_ue_create: created {ue} with {args}") - formation: Formation = db.session.get(Formation, args["formation_id"]) - formation.invalidate_module_coefs() + # caches + ue.formation.invalidate_module_coefs() + ue.formation.invalidate_cached_sems() # news - formation = db.session.get(Formation, args["formation_id"]) ScolarNews.add( typ=ScolarNews.NEWS_FORM, obj=args["formation_id"], - text=f"Modification de la formation {formation.acronyme}", + text=f"Modification de la formation {ue.formation.acronyme}", ) - formation.invalidate_cached_sems() - return ue_id + + return ue def do_ue_delete(ue: UniteEns, delete_validations=False, force=False): @@ -544,13 +502,13 @@ def ue_edit(ue_id=None, create=False, formation_id=None, default_semestre_idx=No tf[2]["numero"] = next_ue_numero( formation_id, int(tf[2]["semestre_idx"]) ) - ue_id = do_ue_create(tf[2]) + ue = do_ue_create(tf[2]) matiere_id = None 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 = Matiere.create_from_dict( - {"ue_id": ue_id, "titre": tf[2]["titre"], "numero": 1} + {"ue_id": ue.id, "titre": tf[2]["titre"], "numero": 1} ) matiere_id = matiere.id if cursus.UE_IS_MODULE: @@ -561,14 +519,13 @@ def ue_edit(ue_id=None, create=False, formation_id=None, default_semestre_idx=No "code": tf[2]["acronyme"], # tous les modules auront coef 1, et on utilisera les ECTS: "coefficient": 1.0, - "ue_id": ue_id, + "ue_id": ue.id, "matiere_id": matiere_id, "formation_id": formation_id, "semestre_id": tf[2]["semestre_idx"], }, ) db.session.commit() - ue = db.session.get(UniteEns, ue_id) flash(f"UE créée (code {ue.ue_code})") else: if not tf[2]["numero"]: @@ -1118,12 +1075,12 @@ def _ue_table_ues( ue.code_apogee or "" }""" - if cur_ue_semestre_id != ue.semestre_id: - cur_ue_semestre_id = ue.semestre_id - if ue.semestre_id == codes_cursus.UE_SEM_DEFAULT: + if cur_ue_semestre_id != ue.get_semestre_id(): + cur_ue_semestre_id = ue.get_semestre_id() + if ue.semestre_idx == codes_cursus.UE_SEM_DEFAULT: lab = "Pas d'indication de semestre:" else: - lab = f"""Semestre {ue.semestre_id}:""" + lab = f"""Semestre {ue.get_semestre_id()}:""" H.append( f'
{lab}
' ) @@ -1205,8 +1162,8 @@ def _ue_table_ues( H.append( f""" + formation_id=ue.formation_id, semestre_idx=ue.get_semestre_id()) + }">Ajouter une UE dans le semestre {ue.get_semestre_id() or ''}
""" ) diff --git a/app/formations/formation_io.py b/app/formations/formation_io.py index f966fdb9e..38fc53d13 100644 --- a/app/formations/formation_io.py +++ b/app/formations/formation_io.py @@ -350,16 +350,15 @@ def formation_import_xml(doc: str | bytes, import_tags=True, use_local_refcomp=F ) # Note: si le code est indiqué "" dans le xml, il faut le conserver vide # pour la comparaison ultérieure des formations XXX - ue_id = edit_ue.do_ue_create(ue_info[1], allow_empty_ue_code=True) - ue: UniteEns = db.session.get(UniteEns, ue_id) + ue = edit_ue.do_ue_create(ue_info[1], allow_empty_ue_code=True) assert ue if xml_ue_id: - ues_old2new[xml_ue_id] = ue_id + ues_old2new[xml_ue_id] = ue.id # élément optionnel présent dans les exports BUT: ue_reference = ue_info[1].get("reference") if ue_reference: - ue_reference_to_id[int(ue_reference)] = ue_id + ue_reference_to_id[int(ue_reference)] = ue.id # -- Create matieres for mat_info in ue_info[2]: @@ -397,7 +396,7 @@ def formation_import_xml(doc: str | bytes, import_tags=True, use_local_refcomp=F continue assert mat_info[0] == "matiere" - mat_info[1]["ue_id"] = ue_id + mat_info[1]["ue_id"] = ue.id mat = Matiere.create_from_dict(mat_info[1]) mat_id = mat.id # -- create modules @@ -410,7 +409,7 @@ def formation_import_xml(doc: str | bytes, import_tags=True, use_local_refcomp=F xml_module_id = None mod_info[1]["formation_id"] = formation.id mod_info[1]["matiere_id"] = mat_id - mod_info[1]["ue_id"] = ue_id + mod_info[1]["ue_id"] = ue.id if not "module_type" in mod_info[1]: mod_info[1]["module_type"] = scu.ModuleType.STANDARD module = Module.create_from_dict( diff --git a/app/models/formsemestre.py b/app/models/formsemestre.py index 4afd6a3f3..cd2f0e7d2 100644 --- a/app/models/formsemestre.py +++ b/app/models/formsemestre.py @@ -19,6 +19,7 @@ from operator import attrgetter from flask_login import current_user from flask import abort, flash, g, url_for +from flask_sqlalchemy.query import Query from sqlalchemy.sql import text from sqlalchemy import func @@ -799,7 +800,7 @@ class FormSemestre(models.ScoDocModel): @classmethod def get_dept_formsemestres_courants( cls, dept: Departement, date_courante: datetime.datetime | None = None - ) -> db.Query: + ) -> Query: """Liste (query) ordonnée des formsemestres courants, c'est à dire contenant la date courant (si None, la date actuelle)""" date_courante = date_courante or db.func.current_date() diff --git a/app/models/ues.py b/app/models/ues.py index f7ad5ea70..dce90b4e6 100644 --- a/app/models/ues.py +++ b/app/models/ues.py @@ -120,7 +120,7 @@ class UniteEns(models.ScoDocModel): if "is_external" in args: args["is_external"] = scu.to_bool(args["is_external"]) if "ects" in args: - args["ects"] = float(args["ects"]) + args["ects"] = None if args["ects"] is None else float(args["ects"]) return args @@ -190,7 +190,7 @@ class UniteEns(models.ScoDocModel): utilisée dans un formsemestre verrouillé ou validations de jury de cette UE. Renvoie aussi une explication. """ - from app.models import FormSemestre, ModuleImpl, ScolarFormSemestreValidation + from app.models import ModuleImpl, ScolarFormSemestreValidation # before 9.7.23: contains modules used in a locked formsemestre # starting from 9.7.23: + existence de validations de jury de cette UE diff --git a/app/scodoc/sco_bulletins_json.py b/app/scodoc/sco_bulletins_json.py index 1352ea655..99a9b8b93 100644 --- a/app/scodoc/sco_bulletins_json.py +++ b/app/scodoc/sco_bulletins_json.py @@ -495,16 +495,16 @@ def dict_decision_jury( # and sco_preferences.get_preference( 'bul_show_uevalid', formsemestre_id): # always publish (car utile pour export Apogee) for ue_id in decision["decisions_ue"].keys(): - ue = edit_ue.ue_list({"ue_id": ue_id})[0] + ue = UniteEns.get_ue(ue_id) d["decision_ue"].append( - dict( - ue_id=ue["ue_id"], - numero=ue["numero"], - acronyme=ue["acronyme"], - titre=ue["titre"], - code=decision["decisions_ue"][ue_id]["code"], - ects=ue["ects"] or "", - ) + { + "ue_id": ue.ue_id, + "numero": ue.numero, + "acronyme": ue.acronyme, + "titre": ue.titre, + "code": decision["decisions_ue"][ue.id]["code"], + "ects": ue.ects or "", + } ) d["autorisation_inscription"] = [] for aut in decision["autorisations"]: @@ -515,7 +515,7 @@ def dict_decision_jury( ) ) else: - d["decision"] = dict(code="", etat="DEM") + d["decision"] = {"code": "", "etat": "DEM"} # Ajout jury BUT: if formsemestre.formation.is_apc(): d.update(but_validations.dict_decision_jury(etud, formsemestre)) diff --git a/app/scodoc/sco_bulletins_xml.py b/app/scodoc/sco_bulletins_xml.py index dfff8feeb..1a2e2d1f3 100644 --- a/app/scodoc/sco_bulletins_xml.py +++ b/app/scodoc/sco_bulletins_xml.py @@ -51,7 +51,7 @@ import app.scodoc.sco_utils as scu import app.scodoc.notesdb as ndb from app import log from app.but.bulletin_but_xml_compat import bulletin_but_xml_compat -from app.models import BulAppreciations, Evaluation, FormSemestre +from app.models import BulAppreciations, Evaluation, FormSemestre, UniteEns from app.scodoc import sco_assiduites from app.scodoc import codes_cursus from app.scodoc import sco_formsemestre @@ -389,19 +389,19 @@ def make_xml_formsemestre_bulletinetud( else: doc.append(Element("decision", code=code, etat=str(etat))) - if decision[ - "decisions_ue" - ]: # and sco_preferences.get_preference( 'bul_show_uevalid', formsemestre_id): always publish (car utile pour export Apogee) + if decision["decisions_ue"]: + # and sco_preferences.get_preference( 'bul_show_uevalid', formsemestre_id): + # always publish (car utile pour export Apogee) for ue_id in decision["decisions_ue"].keys(): - ue = edit_ue.ue_list({"ue_id": ue_id})[0] + ue = UniteEns.get_ue(ue_id) doc.append( Element( "decision_ue", - ue_id=str(ue["ue_id"]), - numero=quote_xml_attr(ue["numero"]), - acronyme=quote_xml_attr(ue["acronyme"]), - titre=quote_xml_attr(ue["titre"]), - code=decision["decisions_ue"][ue_id]["code"], + ue_id=str(ue.id), + numero=quote_xml_attr(ue.numero), + acronyme=quote_xml_attr(ue.acronyme), + titre=quote_xml_attr(ue.titre or ""), + code=decision["decisions_ue"][ue.id]["code"], ) ) diff --git a/app/scodoc/sco_formsemestre_exterieurs.py b/app/scodoc/sco_formsemestre_exterieurs.py index 9b2c59748..6ad279ff0 100644 --- a/app/scodoc/sco_formsemestre_exterieurs.py +++ b/app/scodoc/sco_formsemestre_exterieurs.py @@ -453,7 +453,7 @@ def _ue_form_description( return descr -def _check_values(formsemestre: FormSemestre, ue_list, values): +def _check_values(formsemestre: FormSemestre, ue_list: list[UniteEns], values): """Check that form values are ok for each UE: code != None => note and coef diff --git a/app/scodoc/sco_ue_external.py b/app/scodoc/sco_ue_external.py index fa85beebf..4e74e25dc 100644 --- a/app/scodoc/sco_ue_external.py +++ b/app/scodoc/sco_ue_external.py @@ -96,7 +96,7 @@ def external_ue_create( formation_id = formsemestre.formation.id numero = edit_ue.next_ue_numero(formation_id, semestre_id=formsemestre.semestre_id) - ue_id = edit_ue.do_ue_create( + ue = edit_ue.do_ue_create( { "formation_id": formation_id, "semestre_idx": formsemestre.semestre_id, @@ -108,10 +108,9 @@ def external_ue_create( "is_external": True, }, ) - ue = db.session.get(UniteEns, ue_id) flash(f"UE créée (code {ue.ue_code})") matiere = Matiere.create_from_dict( - {"ue_id": ue_id, "titre": titre or acronyme, "numero": 1} + {"ue_id": ue.id, "titre": titre or acronyme, "numero": 1} ) module = Module.create_from_dict( @@ -119,7 +118,7 @@ def external_ue_create( "titre": "UE extérieure", "code": acronyme, "coefficient": ects, # tous le coef. module est egal à la quantite d'ECTS - "ue_id": ue_id, + "ue_id": ue.id, "matiere_id": matiere.id, "formation_id": formation_id, "semestre_id": formsemestre.semestre_id, diff --git a/tests/unit/sco_fake_gen.py b/tests/unit/sco_fake_gen.py index d9545c961..06a10f3f6 100644 --- a/tests/unit/sco_fake_gen.py +++ b/tests/unit/sco_fake_gen.py @@ -197,11 +197,8 @@ class ScoFake(object): """ if numero is None: numero = edit_ue.next_ue_numero(formation_id, 0) - oid = edit_ue.do_ue_create(locals()) - oids = edit_ue.ue_list(args={"ue_id": oid}) - if not oids: - raise ScoValueError("ue not created !") - return oid + ue = edit_ue.do_ue_create(locals()) + return ue.id @logging_meth def create_matiere(self, ue_id=None, titre=None, numero=0) -> int: diff --git a/tests/unit/test_formations.py b/tests/unit/test_formations.py index a68114891..d047a0708 100644 --- a/tests/unit/test_formations.py +++ b/tests/unit/test_formations.py @@ -51,7 +51,7 @@ from app.formations import ( edit_ue, formation_io, ) -from app.models import Formation, Matiere, Module, ModuleImpl +from app.models import Formation, Matiere, Module, ModuleImpl, UniteEns from app.scodoc import sco_formsemestre from app.scodoc import sco_exceptions from app.scodoc import sco_formsemestre_edit @@ -292,11 +292,9 @@ def test_formations(test_client): li_mat2 = Matiere.query.all() assert len(li_mat2) == 3 # verification de la suppression de la matiere - li_ue = edit_ue.ue_list() - assert len(li_ue) == 4 + assert UniteEns.query.count() == 4 edit_ue.ue_delete(ue_id=uet_id, dialog_confirmed=True) - li_ue2 = edit_ue.ue_list() - assert len(li_ue2) == 3 # verification de la suppression de l'UE + assert UniteEns.query.count() == 3 # verification de la suppression de l'UE # --- Suppression d'une formation @@ -321,9 +319,9 @@ def test_import_formation(test_client, filename="formation-exemple-1.xml"): assert len(f) == 3 # 3-uple formation_id = f[0] # --- Vérification des UE - ues = edit_ue.ue_list({"formation_id": formation_id}) + ues = UniteEns.query.filter_by(formation_id=formation_id).all() assert len(ues) == 10 - assert all(not ue["is_external"] for ue in ues) # aucune UE externe dans le XML + assert all(not ue.is_external for ue in ues) # aucune UE externe dans le XML # --- Mise en place de 4 semestres formsemestre_ids = [ G.create_formsemestre(