' % scu.NotesURL()
+ )
else:
if not dialog_confirmed:
return scu.confirm_dialog(
diff --git a/app/scodoc/sco_edit_ue.py b/app/scodoc/sco_edit_ue.py
index 9f4900bc4e..8fe5fd0211 100644
--- a/app/scodoc/sco_edit_ue.py
+++ b/app/scodoc/sco_edit_ue.py
@@ -118,7 +118,7 @@ def do_ue_create(args):
f"""Acronyme d'UE "{args['acronyme']}" déjà utilisé !
(chaque UE doit avoir un acronyme unique dans la formation)"""
)
- if not "ue_code" in args:
+ if (not "ue_code" in args) or (not args["ue_code"].strip()):
# évite les conflits de code
while True:
cursor = db.session.execute("select notes_newid_ucod();")
@@ -405,6 +405,7 @@ def ue_edit(ue_id=None, create=False, formation_id=None, default_semestre_idx=No
"explanation": """code interne (non vide). Toutes les UE partageant le même code
(et le même code de formation) sont compatibles (compensation de semestres, capitalisation d'UE).
Voir liste ci-dessous.""",
+ "allow_null": False,
},
),
(
@@ -663,6 +664,13 @@ def ue_table(formation_id=None, semestre_idx=1, msg=""): # was ue_list
ues_externes_obj = UniteEns.query.filter_by(
formation_id=formation_id, is_external=True
)
+ # liste ordonnée des formsemestres de cette formation:
+ formsemestres = sorted(
+ FormSemestre.query.filter_by(formation_id=formation_id).all(),
+ key=lambda s: s.sort_key(),
+ reverse=True,
+ )
+
if is_apc:
# Pour faciliter la transition des anciens programmes non APC
for ue in ues_obj:
@@ -901,18 +909,29 @@ du programme" (menu "Semestre") si vous avez un semestre en cours);
"""
)
H.append("
")
- if editable:
+ if has_perm_change:
H.append(
f"""
"""
)
- for formsemestre in sorted(
- FormSemestre.query.filter_by(formation_id=formation_id).all(),
- key=lambda s: s.sort_key(),
- reverse=True,
- ):
+ for formsemestre in formsemestres:
H.append(
f"""
GenTable:
FormSemestre.date_debut
).all()
row["sems_list_txt"] = ", ".join(s.session_id() for s in row["formsemestres"])
- row["_sems_list_txt_html"] = (
- ", ".join(
+ row["_sems_list_txt_html"] = ", ".join(
+ [
f"""{s.session_id()}
- """
+ )}">{s.session_id()}"""
for s in row["formsemestres"]
- )
- + f""", ajouter
"""
+ ]
)
if row["formsemestres"]:
row["date_fin_dernier_sem"] = (
diff --git a/app/scodoc/sco_formsemestre_edit.py b/app/scodoc/sco_formsemestre_edit.py
index 3280b1be50..cd231467b5 100644
--- a/app/scodoc/sco_formsemestre_edit.py
+++ b/app/scodoc/sco_formsemestre_edit.py
@@ -1285,107 +1285,136 @@ def do_formsemestre_clone(
def formsemestre_associate_new_version(
- formsemestre_id,
- other_formsemestre_ids=[],
- dialog_confirmed=False,
+ formation_id: int,
+ formsemestre_id: int = None,
+ other_formsemestre_ids: list[int] = None,
):
- """Formulaire changement formation d'un semestre"""
- formsemestre_id = int(formsemestre_id)
- other_formsemestre_ids = [int(x) for x in other_formsemestre_ids]
- if not dialog_confirmed:
- # dresse le liste des semestres de la meme formation et version
- formsemestre: FormSemestre = FormSemestre.query.get_or_404(formsemestre_id)
- othersems = sco_formsemestre.do_formsemestre_list(
- args={
- "formation_id": formsemestre.formation.id,
- "version": formsemestre.formation.version,
- "etat": "1",
- },
+ """Formulaire nouvelle version formation et association d'un ou plusieurs formsemestre.
+ formation_id: la formation à dupliquer
+ formsemestre_id: optionnel, formsemestre de départ, qui sera associé à la noiuvelle version
+ """
+ if formsemestre_id is not None:
+ formsemestre_id = int(formsemestre_id)
+ formation: Formation = Formation.query.get_or_404(formation_id)
+ other_formsemestre_ids = {int(x) for x in other_formsemestre_ids or []}
+ if request.method == "GET":
+ # dresse la liste des semestres non verrouillés de la même formation
+ other_formsemestres: list[FormSemestre] = formation.formsemestres.filter_by(
+ etat=True
)
+
H = []
- for s in othersems:
- if (
- s["formsemestre_id"] == formsemestre_id
- or s["formsemestre_id"] in other_formsemestre_ids
- ):
- checked = 'checked="checked"'
- else:
- checked = ""
- if s["formsemestre_id"] == formsemestre_id:
- disabled = 'disabled="1"'
- else:
- disabled = ""
+ for other_formsemestre in other_formsemestres:
+ checked = (
+ 'checked="checked"'
+ if (
+ other_formsemestre.id == formsemestre_id
+ or other_formsemestre.id in other_formsemestre_ids
+ )
+ else ""
+ )
+ disabled = (
+ 'disabled="1"' if other_formsemestre.id == formsemestre_id else ""
+ )
+
H.append(
f"""
Le programme pédagogique ("formation") va être dupliqué
- pour que vous puissiez le modifier sans affecter les autres
- semestres. Les autres paramètres (étudiants, notes...) du
- semestre seront inchangés.
+ pour que vous puissiez le modifier sans affecter les semestres déjà terminés.
Veillez à ne pas abuser de cette possibilité, car créer
trop de versions de formations va vous compliquer la gestion
(à vous de garder trace des différences et à ne pas vous
tromper par la suite...).
-
Si vous souhaitez créer un programme pour de futurs semestres,
- utilisez plutôt Créer une nouvelle version.
-
-
Si vous voulez associer aussi d'autres semestres à la nouvelle
- version, cochez-les:
+
Si vous voulez associer des semestres à la nouvelle
+ version, cochez-les maintenant
+ (attention : vous ne pourrez pas le faire plus tard car on ne peut pas
+ changer la formation d'un semestre !):
"""
+ "".join(H)
+ + """
Les données (étudiants, notes...) de ces semestres seront inchangées.
"""
+ "
",
- OK="Associer ces semestres à une nouvelle version",
+ OK="Créer une nouvelle version et y associer ces semestres",
dest_url="",
- cancel_url=url_for(
- "notes.formsemestre_status",
- scodoc_dept=g.scodoc_dept,
- formsemestre_id=formsemestre_id,
- ),
- parameters={"formsemestre_id": formsemestre_id},
+ cancel_url=cancel_url,
+ parameters={"formation_id": formation_id},
)
- else:
- do_formsemestres_associate_new_version(
- [formsemestre_id] + other_formsemestre_ids
+ elif request.method == "POST":
+ if formsemestre_id is not None: # pas dans le form car checkbox disabled
+ other_formsemestre_ids |= {formsemestre_id}
+ new_formation_id = do_formsemestres_associate_new_version(
+ formation_id, other_formsemestre_ids
)
- flash("Semestre associé à une nouvelle version de la formation")
- return flask.redirect(
- url_for(
- "notes.formsemestre_status",
- scodoc_dept=g.scodoc_dept,
- formsemestre_id=formsemestre_id,
+ flash(
+ "Nouvelle version de la formation créée"
+ + (" et semestres associés." if other_formsemestre_ids else ".")
+ )
+ if formsemestre_id is None:
+ return flask.redirect(
+ url_for(
+ "notes.ue_table",
+ scodoc_dept=g.scodoc_dept,
+ formation_id=new_formation_id,
+ )
)
- )
+ else:
+ return flask.redirect(
+ url_for(
+ "notes.formsemestre_status",
+ scodoc_dept=g.scodoc_dept,
+ formsemestre_id=formsemestre_id,
+ )
+ )
+ else:
+ raise ScoValueError("Méthode invalide")
-def do_formsemestres_associate_new_version(formsemestre_ids):
- """Cree une nouvelle version de la formation du semestre, et y rattache les semestres.
+def do_formsemestres_associate_new_version(
+ formation_id: int, formsemestre_ids: list[int]
+) -> int:
+ """Crée une nouvelle version de la formation du semestre, et y rattache les semestres.
Tous les moduleimpl sont ré-associés à la nouvelle formation, ainsi que les decisions de jury
si elles existent (codes d'UE validées).
- Les semestre doivent tous appartenir à la meme version de la formation
+ Les semestre doivent tous appartenir à la meme version de la formation.
+ renvoie l'id de la nouvelle formation.
"""
- log(f"do_formsemestres_associate_new_version {formsemestre_ids}")
- if not formsemestre_ids:
- return
- # Check: tous de la même formation
- assert isinstance(formsemestre_ids[0], int)
- sem = sco_formsemestre.get_formsemestre(formsemestre_ids[0])
- formation_id = sem["formation_id"]
- for formsemestre_id in formsemestre_ids[1:]:
- assert isinstance(formsemestre_id, int)
- sem = sco_formsemestre.get_formsemestre(formsemestre_id)
- if formation_id != sem["formation_id"]:
- raise ScoValueError("les semestres ne sont pas tous de la même formation !")
+ log(f"do_formsemestres_associate_new_version {formation_id} {formsemestre_ids}")
+
+ # Check: tous les semestre de la formation
+ formsemestres = [FormSemestre.query.get_or_404(i) for i in formsemestre_ids]
+ if not all(
+ [formsemestre.formation_id == formation_id for formsemestre in formsemestres]
+ ):
+ raise ScoValueError("les semestres ne sont pas tous de la même formation !")
cnx = ndb.GetDBConnexion()
# New formation:
@@ -1414,6 +1443,7 @@ def do_formsemestres_associate_new_version(formsemestre_ids):
_reassociate_moduleimpls(cnx, formsemestre_id, ues_old2new, modules_old2new)
cnx.commit()
+ return formation_id
def _reassociate_moduleimpls(cnx, formsemestre_id, ues_old2new, modules_old2new):
@@ -1676,7 +1706,7 @@ def formsemestre_change_publication_bul(
msg = ""
return scu.confirm_dialog(
"
Confirmer la %s publication des bulletins ?
" % msg,
- helpmsg="""Il est parfois utile de désactiver la diffusion des bulletins,
+ help_msg="""Il est parfois utile de désactiver la diffusion des bulletins,
par exemple pendant la tenue d'un jury ou avant harmonisation des notes.
Ce réglage n'a d'effet que si votre établissement a interfacé ScoDoc et un portail étudiant.
diff --git a/app/scodoc/sco_formsemestre_status.py b/app/scodoc/sco_formsemestre_status.py
index 32478d0b52..279ab55425 100644
--- a/app/scodoc/sco_formsemestre_status.py
+++ b/app/scodoc/sco_formsemestre_status.py
@@ -236,7 +236,10 @@ def formsemestre_status_menubar(formsemestre: FormSemestre) -> str:
{
"title": "Associer à une nouvelle version du programme",
"endpoint": "notes.formsemestre_associate_new_version",
- "args": {"formsemestre_id": formsemestre_id},
+ "args": {
+ "formsemestre_id": formsemestre_id,
+ "formation_id": formsemestre.formation_id,
+ },
"enabled": current_user.has_permission(Permission.ScoChangeFormation)
and formsemestre.etat,
"helpmsg": "",
diff --git a/app/scodoc/sco_utils.py b/app/scodoc/sco_utils.py
index 6ff484ca8f..dc4fb9772a 100644
--- a/app/scodoc/sco_utils.py
+++ b/app/scodoc/sco_utils.py
@@ -1075,16 +1075,18 @@ def query_portal(req, msg="Portail Apogee", timeout=3):
def confirm_dialog(
message="
Confirmer ?
",
OK="OK",
- Cancel="Annuler",
- dest_url="",
- cancel_url="",
- target_variable="dialog_confirmed",
- parameters={},
add_headers=True, # complete page
- helpmsg=None,
+ cancel_label="Annuler",
+ cancel_url="",
+ dest_url="",
+ help_msg=None,
+ parameters: dict = None,
+ target_variable="dialog_confirmed",
):
+ """HTML confirmation dialog: submit (POST) to same page or dest_url if given."""
from app.scodoc import html_sco_header
+ parameters = parameters or {}
# dialog de confirmation simple
parameters[target_variable] = 1
# Attention: la page a pu etre servie en GET avec des parametres
@@ -1105,24 +1107,22 @@ def confirm_dialog(
H.append(f'')
if cancel_url:
H.append(
- """"""
- % (Cancel, cancel_url)
+ f""""""
)
for param in parameters.keys():
if parameters[param] is None:
parameters[param] = ""
- if type(parameters[param]) == type([]):
+ if isinstance(parameters[param], list):
for e in parameters[param]:
- H.append('' % (param, e))
+ H.append(f"""""")
else:
H.append(
- ''
- % (param, parameters[param])
+ f""""""
)
H.append("")
- if helpmsg:
- H.append('
",
- helpmsg="""Les notes d'un semestre verrouillé ne peuvent plus être modifiées.
+ help_msg="""Les notes d'un semestre verrouillé ne peuvent plus être modifiées.
Un semestre verrouillé peut cependant être déverrouillé facilement à tout moment
(par son responsable ou un administrateur).
diff --git a/tests/unit/test_formsemestre.py b/tests/unit/test_formsemestre.py
index ee25669531..4ca9a551a5 100644
--- a/tests/unit/test_formsemestre.py
+++ b/tests/unit/test_formsemestre.py
@@ -105,7 +105,9 @@ def test_formsemestre_misc_views(test_client):
assert isinstance(ans, (str, Response)) # ici str
# Juste la page dialogue avant opération::
ans = sco_formsemestre_edit.formsemestre_clone(formsemestre.id)
- ans = sco_formsemestre_edit.formsemestre_associate_new_version(formsemestre.id)
+ ans = sco_formsemestre_edit.formsemestre_associate_new_version(
+ formsemestre.formation_id, formsemestre.id
+ )
ans = sco_formsemestre_edit.formsemestre_delete(formsemestre.id)
# ----- MENU INSCRIPTIONS