diff --git a/app/models/formsemestre.py b/app/models/formsemestre.py index b44498007..9aa6cdd4b 100644 --- a/app/models/formsemestre.py +++ b/app/models/formsemestre.py @@ -1040,6 +1040,33 @@ class FormSemestre(db.Model): nb_recorded += 1 return nb_recorded + def change_formation(self, formation_dest: Formation): + """Associe ce formsemestre à une autre formation. + Ce n'est possible que si la formation destination possède des modules de + même code que ceux utilisés dans la formation d'origine du formsemestre. + S'il manque un module, l'opération est annulée. + Commit (or rollback) session. + """ + ok = True + for mi in self.modimpls: + dest_modules = formation_dest.modules.filter_by(code=mi.module.code).all() + match len(dest_modules): + case 1: + mi.module = dest_modules[0] + db.session.add(mi) + case 0: + print(f"Argh ! no module found with code={mi.module.code}") + ok = False + case _: + print(f"Arg ! several modules found with code={mi.module.code}") + ok = False + + if ok: + self.formation_id = formation_dest.id + db.session.commit() + else: + db.session.rollback() + # Association id des utilisateurs responsables (aka directeurs des etudes) du semestre notes_formsemestre_responsables = db.Table( diff --git a/app/scodoc/sco_edit_formation.py b/app/scodoc/sco_edit_formation.py index 2327071ba..0857656df 100644 --- a/app/scodoc/sco_edit_formation.py +++ b/app/scodoc/sco_edit_formation.py @@ -80,7 +80,7 @@ def formation_delete(formation_id=None, dialog_confirmed=False): f"""

Confirmer la suppression de la formation {formation.titre} ({formation.acronyme}) ?

-

Attention: la suppression d'une formation est irréversible +

Attention: la suppression d'une formation est irréversible et implique la supression de toutes les UE, matières et modules de la formation !

""", @@ -273,7 +273,8 @@ def formation_edit(formation_id=None, create=False): "\n".join(H) + tf_error_message( f"""Valeurs incorrectes: il existe déjà une formation avec même titre, acronyme et version. """ @@ -285,11 +286,11 @@ def formation_edit(formation_id=None, create=False): if create: formation = do_formation_create(tf[2]) else: - do_formation_edit(tf[2]) - flash( - f"""Création de la formation { - formation.titre} ({formation.acronyme}) version {formation.version}""" - ) + if do_formation_edit(tf[2]): + flash( + f"""Modification de la formation { + formation.titre} ({formation.acronyme}) version {formation.version}""" + ) return flask.redirect( url_for( "notes.ue_table", scodoc_dept=g.scodoc_dept, formation_id=formation.id @@ -335,8 +336,8 @@ def do_formation_create(args: dict) -> Formation: return formation -def do_formation_edit(args): - "edit a formation" +def do_formation_edit(args) -> bool: + "edit a formation, returns True if modified" # On ne peut jamais supprimer le code formation: if "formation_code" in args and not args["formation_code"]: @@ -350,11 +351,16 @@ def do_formation_edit(args): if "type_parcours" in args: del args["type_parcours"] + modified = False for field in formation.__dict__: if field in args: value = args[field].strip() if isinstance(args[field], str) else args[field] - if field and field[0] != "_": + if field and field[0] != "_" and getattr(formation, field, None) != value: setattr(formation, field, value) + modified = True + + if not modified: + return False db.session.add(formation) try: @@ -370,6 +376,7 @@ def do_formation_edit(args): ), ) from exc formation.invalidate_cached_sems() + return True def module_move(module_id, after=0, redirect=True): diff --git a/app/scodoc/sco_formations.py b/app/scodoc/sco_formations.py index dbd864f53..d18109871 100644 --- a/app/scodoc/sco_formations.py +++ b/app/scodoc/sco_formations.py @@ -307,7 +307,7 @@ def formation_import_xml(doc: str, import_tags=True, use_local_refcomp=False): D = sco_xml.xml_to_dicts(f) except Exception as exc: raise ScoFormatError( - """Ce document xml ne correspond pas à un programme exporté par ScoDoc. + """Ce document xml ne correspond pas à un programme exporté par ScoDoc. (élément 'formation' inexistant par exemple).""" ) from exc assert D[0] == "formation" @@ -322,8 +322,13 @@ def formation_import_xml(doc: str, import_tags=True, use_local_refcomp=False): referentiel_competence_id = _formation_retreive_refcomp(f_dict) f_dict["referentiel_competence_id"] = referentiel_competence_id # find new version number + acronyme_lower = f_dict["acronyme"].lower if f_dict["acronyme"] else "" + titre_lower = f_dict["titre"].lower if f_dict["titre"] else "" formations: list[Formation] = Formation.query.filter_by( - acronyme=f_dict["acronyme"], titre=f_dict["titre"], dept_id=f_dict["dept_id"] + dept_id=f_dict["dept_id"] + ).filter( + db.func.lower(Formation.acronyme) == acronyme_lower, + db.func(Formation.titre) == titre_lower, ) if formations.count(): version = max(f.version or 0 for f in formations) @@ -518,6 +523,7 @@ def formation_list_table() -> GenTable: "_titre_link_class": "stdlink", "_titre_id": f"""titre-{acronyme_no_spaces}""", "version": formation.version or 0, + "commentaire": formation.commentaire or "", } # Ajoute les semestres associés à chaque formation: row["formsemestres"] = formation.formsemestres.order_by( @@ -594,10 +600,12 @@ def formation_list_table() -> GenTable: "formation_code", "version", "titre", + "commentaire", "sems_list_txt", ) titles = { "buttons": "", + "commentaire": "Commentaire", "acronyme": "Acro.", "parcours_name": "Type", "titre": "Titre", diff --git a/app/static/css/scodoc.css b/app/static/css/scodoc.css index da2dfe36d..0f9072c81 100644 --- a/app/static/css/scodoc.css +++ b/app/static/css/scodoc.css @@ -2319,7 +2319,10 @@ table.formation_list_table td.buttons span.but_placeholder { } .formation_list_table td.titre { - width: 50%; + width: 45%; +} +.formation_list_table td.commentaire { + font-style: italic; } .formation_list_table td.sems_list_txt { diff --git a/sco_version.py b/sco_version.py index 67d9697b6..26bbad3c4 100644 --- a/sco_version.py +++ b/sco_version.py @@ -1,7 +1,7 @@ # -*- mode: python -*- # -*- coding: utf-8 -*- -SCOVERSION = "9.6.56" +SCOVERSION = "9.6.57" SCONAME = "ScoDoc"