diff --git a/app/scodoc/sco_formsemestre_edit.py b/app/scodoc/sco_formsemestre_edit.py index e6ac86e3a8..b1f6faa686 100644 --- a/app/scodoc/sco_formsemestre_edit.py +++ b/app/scodoc/sco_formsemestre_edit.py @@ -28,13 +28,16 @@ """Form choix modules / responsables et creation formsemestre """ import flask -from flask import url_for, g, request +from flask import url_for, flash +from flask import g, request from flask_login import current_user from app import db from app.auth.models import User from app.models import APO_CODE_STR_LEN, SHORT_STR_LEN -from app.models import ModuleImpl, Evaluation, EvaluationUEPoids +from app.models import Module, ModuleImpl, Evaluation, EvaluationUEPoids +from app.models.formations import Formation +from app.models.formsemestre import FormSemestre import app.scodoc.notesdb as ndb import app.scodoc.sco_utils as scu from app.scodoc import sco_cache @@ -65,9 +68,9 @@ from app.scodoc import sco_preferences from app.scodoc import sco_users -def _default_sem_title(F): - """Default title for a semestre in formation F""" - return F["titre"] +def _default_sem_title(formation): + """Default title for a semestre in formation""" + return formation.titre def formsemestre_createwithmodules(): @@ -140,6 +143,7 @@ def do_formsemestre_createwithmodules(edit=False): if edit: formsemestre_id = int(vals["formsemestre_id"]) sem = sco_formsemestre.get_formsemestre(formsemestre_id) + formsemestre = FormSemestre.query.get_or_404(formsemestre_id) if not current_user.has_permission(Permission.ScoImplement): if not edit: # il faut ScoImplement pour creer un semestre @@ -161,26 +165,25 @@ def do_formsemestre_createwithmodules(edit=False): allowed_user_names = list(uid2display.values()) + [""] # formation_id = int(vals["formation_id"]) - F = sco_formations.formation_list(args={"formation_id": formation_id}) - if not F: + formation = Formation.query.get(formation_id) + if formation is None: raise ScoValueError("Formation inexistante !") - F = F[0] if not edit: - initvalues = {"titre": _default_sem_title(F)} + initvalues = {"titre": _default_sem_title(formation)} semestre_id = int(vals["semestre_id"]) - sem_module_ids = set() + module_ids_set = set() else: # setup form init values initvalues = sem semestre_id = initvalues["semestre_id"] # add associated modules to tf-checked: - ams = sco_moduleimpl.moduleimpl_list(formsemestre_id=formsemestre_id) - sem_module_ids = set([x["module_id"] for x in ams]) - initvalues["tf-checked"] = ["MI" + str(x["module_id"]) for x in ams] - for x in ams: - initvalues["MI" + str(x["module_id"])] = uid2display.get( - x["responsable_id"], - f"inconnu numéro {x['responsable_id']} resp. de {x['moduleimpl_id']} !", + module_ids_existing = [modimpl.module.id for modimpl in formsemestre.modimpls] + module_ids_set = set(module_ids_existing) + initvalues["tf-checked"] = ["MI" + str(x) for x in module_ids_existing] + for modimpl in formsemestre.modimpls: + initvalues[f"MI{modimpl.module.id}"] = uid2display.get( + modimpl.responsable_id, + f"inconnu numéro {modimpl.responsable_id} resp. de {modimpl.id} !", ) initvalues["responsable_id"] = uid2display.get( @@ -192,15 +195,20 @@ def do_formsemestre_createwithmodules(edit=False): ) # Liste des ID de semestres - if F["type_parcours"] is not None: - parcours = sco_codes_parcours.get_parcours_from_code(F["type_parcours"]) + if formation.type_parcours is not None: + parcours = sco_codes_parcours.get_parcours_from_code(formation.type_parcours) NB_SEM = parcours.NB_SEM else: NB_SEM = 10 # fallback, max 10 semestres if NB_SEM == 1: semestre_id_list = [-1] else: - semestre_id_list = [-1] + list(range(1, NB_SEM + 1)) + if edit and formation.is_apc(): + # en APC, ne permet pas de changer de semestre + semestre_id_list = [formsemestre.semestre_id] + else: + semestre_id_list = [-1] + list(range(1, NB_SEM + 1)) + semestre_id_labels = [] for sid in semestre_id_list: if sid == -1: @@ -319,7 +327,7 @@ def do_formsemestre_createwithmodules(edit=False): "explanation": """n'indiquez pas les dates, ni le semestre, ni la modalité dans le titre: ils seront automatiquement ajoutés """ - % _default_sem_title(F), + % _default_sem_title(formation), }, ), ( @@ -340,6 +348,9 @@ def do_formsemestre_createwithmodules(edit=False): "title": "Semestre dans la formation", "allowed_values": semestre_id_list, "labels": semestre_id_labels, + "explanation": "en BUT, on ne peut pas modifier le semestre après création" + if formation.is_apc() + else "", }, ), ) @@ -549,7 +560,12 @@ def do_formsemestre_createwithmodules(edit=False): ) ) for mod in mods: - if mod["semestre_id"] == semestre_id: + if mod["semestre_id"] == semestre_id and ( + (not edit) # creation => tous modules + or (not formation.is_apc()) # pas BUT, on peux mixer les semestres + or (semestre_id == formsemestre.semestre_id) # module du semestre + or (mod["module_id"] in module_ids_set) # module déjà présent + ): nbmod += 1 if edit: select_name = "%s!group_id" % mod["module_id"] @@ -560,7 +576,7 @@ def do_formsemestre_createwithmodules(edit=False): else: return "" - if mod["module_id"] in sem_module_ids: + if mod["module_id"] in module_ids_set: disabled = "disabled" else: disabled = "" @@ -684,12 +700,13 @@ def do_formsemestre_createwithmodules(edit=False): msg = '' if tf[0] == 0 or msg: - return ( - '

Formation %(titre)s (%(acronyme)s), version %(version)s, code %(formation_code)s

' - % F - + msg - + str(tf[1]) - ) + return f"""

Formation {formation.titre} ({formation.acronyme}), version {formation.version}, code {formation.formation_code} +

+ {msg} + {tf[1]} + """ elif tf[0] == -1: return "

annulation

" else: @@ -735,42 +752,58 @@ def do_formsemestre_createwithmodules(edit=False): etape=tf[2]["etape_apo" + str(n)], vdi=tf[2]["vdi_apo" + str(n)] ) ) + # Modules sélectionnés: + # (retire le "MI" du début du nom de champs) + module_ids_checked = [int(x[2:]) for x in tf[2]["tf-checked"]] if not edit: - # creation du semestre + if formation.is_apc(): + _formsemestre_check_module_list( + module_ids_checked, tf[2]["semestre_id"] + ) + # création du semestre formsemestre_id = sco_formsemestre.do_formsemestre_create(tf[2]) - # creation des modules - for module_id in tf[2]["tf-checked"]: - assert module_id[:2] == "MI" + # création des modules + for module_id in module_ids_checked: modargs = { - "module_id": int(module_id[2:]), + "module_id": module_id, "formsemestre_id": formsemestre_id, - "responsable_id": tf[2][module_id], + "responsable_id": tf[2][f"MI{module_id}"], } _ = sco_moduleimpl.do_moduleimpl_create(modargs) + flash("Nouveau semestre créé") return flask.redirect( - "formsemestre_status?formsemestre_id=%s&head_message=Nouveau%%20semestre%%20créé" - % formsemestre_id + url_for( + "notes.formsemestre_status", + scodoc_dept=g.scodoc_dept, + formsemestre_id=formsemestre_id, + ) ) else: - # modification du semestre: + # Modification du semestre: # on doit creer les modules nouvellement selectionnés - # modifier ceux a modifier, et DETRUIRE ceux qui ne sont plus selectionnés. - # Note: la destruction echouera s'il y a des objets dependants - # (eg des evaluations définies) - # nouveaux modules - # (retire le "MI" du début du nom de champs) - checkedmods = [int(x[2:]) for x in tf[2]["tf-checked"]] + # modifier ceux à modifier, et DETRUIRE ceux qui ne sont plus selectionnés. + # Note: la destruction échouera s'il y a des objets dépendants + # (eg des évaluations définies) + module_ids_tocreate = [ + x for x in module_ids_checked if not x in module_ids_existing + ] + if formation.is_apc(): + _formsemestre_check_module_list( + module_ids_tocreate, tf[2]["semestre_id"] + ) + # modules existants à modifier + module_ids_toedit = [ + x for x in module_ids_checked if x in module_ids_existing + ] + # modules à détruire + module_ids_todelete = [ + x for x in module_ids_existing if not x in module_ids_checked + ] + # sco_formsemestre.do_formsemestre_edit(tf[2]) - ams = sco_moduleimpl.moduleimpl_list(formsemestre_id=formsemestre_id) - existingmods = [x["module_id"] for x in ams] - mods_tocreate = [x for x in checkedmods if not x in existingmods] - # modules a existants a modifier - mods_toedit = [x for x in checkedmods if x in existingmods] - # modules a detruire - mods_todelete = [x for x in existingmods if not x in checkedmods] # msg = [] - for module_id in mods_tocreate: + for module_id in module_ids_tocreate: modargs = { "module_id": module_id, "formsemestre_id": formsemestre_id, @@ -808,9 +841,11 @@ def do_formsemestre_createwithmodules(edit=False): % (module_id, moduleimpl_id) ) # - ok, diag = formsemestre_delete_moduleimpls(formsemestre_id, mods_todelete) + ok, diag = formsemestre_delete_moduleimpls( + formsemestre_id, module_ids_todelete + ) msg += diag - for module_id in mods_toedit: + for module_id in module_ids_toedit: moduleimpl_id = sco_moduleimpl.moduleimpl_list( formsemestre_id=formsemestre_id, module_id=module_id )[0]["moduleimpl_id"] @@ -847,6 +882,22 @@ def do_formsemestre_createwithmodules(edit=False): ) +def _formsemestre_check_module_list(module_ids, semestre_idx): + """En APC: Vérifie que tous les modules de la liste + sont dans le semestre indiqué. + Sinon, raise ScoValueError. + """ + # vérification de la cohérence / modules / semestre + mod_sems_idx = { + Module.query.get_or_404(module_id).ue.semestre_idx for module_id in module_ids + } + if mod_sems_idx and mod_sems_idx != {semestre_idx}: + raise ScoValueError( + "Les modules sélectionnés ne sont pas tous dans le semestre choisi !", + dest_url="javascript:history.back();", + ) + + def formsemestre_delete_moduleimpls(formsemestre_id, module_ids_to_del): """Delete moduleimpls module_ids_to_del: list of module_id (warning: not moduleimpl)