# -*- coding: utf-8 -*- ############################################################################## # # Gestion scolarite IUT # # Copyright (c) 1999 - 2023 Emmanuel Viennet. All rights reserved. # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # # Emmanuel Viennet emmanuel.viennet@viennet.net # ############################################################################## """Association de nouvelles versions de formation à des formsemestre """ import flask from flask import url_for, flash from flask import g, request from app import db from app.models import ( Module, ModuleImpl, Evaluation, EvaluationUEPoids, UniteEns, ) 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 import log from app.scodoc.sco_exceptions import ScoValueError from app.scodoc import sco_etud from app.scodoc import sco_formations from app.scodoc import sco_formsemestre from app.scodoc import sco_moduleimpl from app.scodoc import sco_cursus_dut def formsemestre_associate_new_version( formation_id: int, formsemestre_id: int = None, other_formsemestre_ids: list[int] = None, ): """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 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"""
{other_formsemestre.titre_mois()}
""" ) if formsemestre_id is None: cancel_url = url_for( "notes.ue_table", scodoc_dept=g.scodoc_dept, formation_id=formation.id ) else: cancel_url = url_for( "notes.formsemestre_status", scodoc_dept=g.scodoc_dept, formsemestre_id=formsemestre_id, ) return scu.confirm_dialog( ( """

Associer à une nouvelle version de formation non verrouillée ?

""" if formsemestre_id else """

Créer une nouvelle version de la formation ?

""" ) + f"""

Formation: {formation.titre} version {formation.version}

Le programme pédagogique ("formation") va être dupliqué 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 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.

Vous pouvez aussi essayer d'associer ce semestre à une autre formation identique.
""", OK="Créer une nouvelle version et y associer ces semestres", dest_url="", cancel_url=cancel_url, parameters={ "formation_id": formation_id, "formsemestre_id": formsemestre_id, }, ) 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( "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( 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. renvoie l'id de la nouvelle 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: ( formation_id, modules_old2new, ues_old2new, ) = sco_formations.formation_create_new_version(formation_id, redirect=False) # Log new ues: for ue_id in ues_old2new: ue = UniteEns.query.get(ue_id) new_ue = UniteEns.query.get(ues_old2new[ue_id]) assert ue.semestre_idx == new_ue.semestre_idx log(f"{ue} -> {new_ue}") # Log new modules for module_id in modules_old2new: mod = Module.query.get(module_id) new_mod = Module.query.get(modules_old2new[module_id]) assert mod.semestre_id == new_mod.semestre_id log(f"{mod} -> {new_mod}") # re-associate for formsemestre_id in formsemestre_ids: sem = sco_formsemestre.get_formsemestre(formsemestre_id) sem["formation_id"] = formation_id sco_formsemestre.do_formsemestre_edit(sem, cnx=cnx, html_quote=False) _reassociate_moduleimpls(cnx, formsemestre_id, ues_old2new, modules_old2new) cnx.commit() return formation_id def _reassociate_moduleimpls(cnx, formsemestre_id, ues_old2new, modules_old2new): """Associe les moduleimpls d'un semestre existant à un autre programme et met à jour les décisions de jury (validations d'UE). """ # re-associate moduleimpls to new modules: modimpls = sco_moduleimpl.moduleimpl_list(formsemestre_id=formsemestre_id) for mod in modimpls: mod["module_id"] = modules_old2new[mod["module_id"]] sco_moduleimpl.do_moduleimpl_edit(mod, formsemestre_id=formsemestre_id) # Update poids des évaluations # les poids associent les évaluations aux UE (qui ont changé d'id) for poids in EvaluationUEPoids.query.filter( EvaluationUEPoids.evaluation_id == Evaluation.id, Evaluation.moduleimpl_id == ModuleImpl.id, ModuleImpl.formsemestre_id == formsemestre_id, ): poids.ue_id = ues_old2new[poids.ue_id] db.session.add(poids) db.session.commit() # update decisions: events = sco_etud.scolar_events_list(cnx, args={"formsemestre_id": formsemestre_id}) for e in events: if e["ue_id"]: e["ue_id"] = ues_old2new[e["ue_id"]] sco_etud.scolar_events_edit(cnx, e) validations = sco_cursus_dut.scolar_formsemestre_validation_list( cnx, args={"formsemestre_id": formsemestre_id} ) for e in validations: if e["ue_id"]: e["ue_id"] = ues_old2new[e["ue_id"]] # log('e=%s' % e ) sco_cursus_dut.scolar_formsemestre_validation_edit(cnx, e) def formations_are_equals( formation1: Formation, formation2: Formation = None, formation2_dict: dict = None ) -> bool: """True if the two formations are exactly the same, except for their versions. Can specify either formation2 or its dict repr. """ fd1 = sco_formations.formation_export_dict( formation1, export_external_ues=True, ue_reference_style="acronyme" ) if formation2_dict is None: if formation2 is None: raise ValueError("must specify formation2 or formation2_dict") formation2_dict = sco_formations.formation_export_dict( formation2, export_external_ues=True, ue_reference_style="acronyme" ) del fd1["version"] if "version" in formation2_dict: del formation2_dict["version"] return fd1 == formation2_dict def formsemestre_change_formation(formsemestre: FormSemestre, new_formation: Formation): """Change la formation d'un semestre. La nouvelle formation doit avoir exactement le même contenu que l'actuelle, à l'exception du numéro de version. """ if not formations_are_equals(formsemestre.formation, new_formation): raise ScoValueError( "formsemestre_change_formation: les deux formations diffèrent" ) log( f"formsemestre_change_formation: formsemestre {formsemestre} to formation {new_formation}" ) # Il faut ré-associer tous les modimpls for modimpl in formsemestre.modimpls: old_module: Module = modimpl.module new_module: Module = ( Module.query.filter_by( formation_id=new_formation.id, code=old_module.code, titre=old_module.titre, ) .join(UniteEns) .filter_by(acronyme=old_module.ue.acronyme) .first() ) if new_module is None: raise ValueError( f"formsemestre_change_formation: erreur sur module {old_module}" ) modimpl.module = new_module db.session.add(modimpl) formsemestre.formation = new_formation db.session.commit()