diff --git a/app/models/formations.py b/app/models/formations.py index 38309a5b8..240f2cdc4 100644 --- a/app/models/formations.py +++ b/app/models/formations.py @@ -86,6 +86,15 @@ class UniteEns(db.Model): def __repr__(self): return f"<{self.__class__.__name__}(id={self.id}, formation_id={self.formation_id}, acronyme='{self.acronyme}')>" + def is_locked(self): + """True if UE should not be modified + (contains modules used in a locked formsemestre) + """ + # XXX todo : à ré-écrire avec SQLAlchemy + from app.scodoc import sco_edit_ue + + return sco_edit_ue.ue_is_locked(self.id) + class Matiere(db.Model): """Matières: regroupe les modules d'une UE diff --git a/app/scodoc/html_sco_header.py b/app/scodoc/html_sco_header.py index 7e35c3b33..0a63d5d19 100644 --- a/app/scodoc/html_sco_header.py +++ b/app/scodoc/html_sco_header.py @@ -87,15 +87,18 @@ Problème de connexion (identifiant, mot de passe): contacter votre responsa ) -_HTML_BEGIN = """ - - +_HTML_BEGIN = """ + + -%(page_title)s + + + +%(page_title)s diff --git a/app/scodoc/sco_edit_apc.py b/app/scodoc/sco_edit_apc.py index 2652c4f91..baafdbc2d 100644 --- a/app/scodoc/sco_edit_apc.py +++ b/app/scodoc/sco_edit_apc.py @@ -38,6 +38,7 @@ from app.scodoc.sco_utils import ModuleType def html_edit_formation_apc( formation, + semestre_idx=None, editable=True, tag_editable=True, ): @@ -48,75 +49,84 @@ def html_edit_formation_apc( """ parcours = formation.get_parcours() assert parcours.APC_SAE - ressources = formation.modules.filter_by(module_type=ModuleType.RESSOURCE) + ressources = formation.modules.filter_by(module_type=ModuleType.RESSOURCE).order_by( + Module.semestre_id, Module.numero + ) saes = formation.modules.filter_by(module_type=ModuleType.SAE) + if semestre_idx is None: + semestre_ids = range(1, parcours.NB_SEM + 1) + else: + semestre_ids = [semestre_idx] other_modules = formation.modules.filter( - Module.module_type != ModuleType.SAE - and Module.module_type != ModuleType.RESSOURCE + Module.module_type != ModuleType.SAE, Module.module_type != ModuleType.RESSOURCE ) arrow_up, arrow_down, arrow_none = sco_groups.get_arrow_icons_tags() - delete_icon = scu.icontag( - "delete_small_img", title="Supprimer (module inutilisé)", alt="supprimer" - ) - delete_disabled_icon = scu.icontag( - "delete_small_dis_img", title="Suppression impossible (module utilisé)" - ) + + icons = { + "arrow_up": arrow_up, + "arrow_down": arrow_down, + "arrow_none": arrow_none, + "delete": scu.icontag( + "delete_small_img", + title="Supprimer (module inutilisé)", + alt="supprimer", + ), + "delete_disabled": scu.icontag( + "delete_small_dis_img", title="Suppression impossible (module utilisé)" + ), + } + H = [ render_template( "pn/form_ues.html", formation=formation, + semestre_ids=semestre_ids, editable=editable, - arrow_up=arrow_up, - arrow_down=arrow_down, - arrow_none=arrow_none, - delete_icon=delete_icon, - delete_disabled_icon=delete_disabled_icon, - ), - render_template( - "pn/form_mods.html", - formation=formation, - titre="Ressources", - create_element_msg="créer une nouvelle ressource", - modules=ressources, - module_type=ModuleType.RESSOURCE, - editable=editable, - arrow_up=arrow_up, - arrow_down=arrow_down, - arrow_none=arrow_none, - delete_icon=delete_icon, - delete_disabled_icon=delete_disabled_icon, - scu=scu, - ), - render_template( - "pn/form_mods.html", - formation=formation, - titre="Situations d'Apprentissage et d'Évaluation (SAÉs)", - create_element_msg="créer une nouvelle SAÉ", - modules=saes, - module_type=ModuleType.SAE, - editable=editable, - arrow_up=arrow_up, - arrow_down=arrow_down, - arrow_none=arrow_none, - delete_icon=delete_icon, - delete_disabled_icon=delete_disabled_icon, - scu=scu, - ), - render_template( - "pn/form_mods.html", - formation=formation, - titre="Autres modules (non BUT)", - create_element_msg="créer un nouveau module", - modules=other_modules, - module_type=ModuleType.STANDARD, - editable=editable, - arrow_up=arrow_up, - arrow_down=arrow_down, - arrow_none=arrow_none, - delete_icon=delete_icon, - delete_disabled_icon=delete_disabled_icon, - scu=scu, + tag_editable=tag_editable, + icons=icons, ), ] + for semestre_idx in semestre_ids: + ressources_in_sem = ressources.filter_by(semestre_id=semestre_idx) + saes_in_sem = saes.filter_by(semestre_id=semestre_idx) + other_modules_in_sem = other_modules.filter_by(semestre_id=semestre_idx) + H += [ + render_template( + "pn/form_mods.html", + formation=formation, + titre=f"Ressources du S{semestre_idx}", + create_element_msg="créer une nouvelle ressource", + modules=ressources_in_sem, + module_type=ModuleType.RESSOURCE, + editable=editable, + tag_editable=tag_editable, + icons=icons, + scu=scu, + ), + render_template( + "pn/form_mods.html", + formation=formation, + titre=f"Situations d'Apprentissage et d'Évaluation (SAÉs) S{semestre_idx}", + create_element_msg="créer une nouvelle SAÉ", + modules=saes_in_sem, + module_type=ModuleType.SAE, + editable=editable, + tag_editable=tag_editable, + icons=icons, + scu=scu, + ), + render_template( + "pn/form_mods.html", + formation=formation, + titre=f"Autres modules (non BUT) du S{semestre_idx}", + create_element_msg="créer un nouveau module", + modules=other_modules_in_sem, + module_type=ModuleType.STANDARD, + editable=editable, + tag_editable=tag_editable, + icons=icons, + scu=scu, + ), + ] return "\n".join(H) diff --git a/app/scodoc/sco_edit_module.py b/app/scodoc/sco_edit_module.py index b61854d28..d6aa1e251 100644 --- a/app/scodoc/sco_edit_module.py +++ b/app/scodoc/sco_edit_module.py @@ -124,15 +124,25 @@ def module_create(matiere_id=None, module_type=None, semestre_id=None): object_name = "Module" H = [ html_sco_header.sco_header(page_title=f"Création {object_name}"), - f"""

Création {object_name} dans la matière {matiere.titre}, - (UE {ue.acronyme})

- """, + ] + if is_apc: + H += [ + f"""

Création {object_name} dans la formation {ue.formation.acronyme}

""" + ] + else: + H += [ + f"""

Création {object_name} dans la matière {matiere.titre}, + (UE {ue.acronyme})

+ """ + ] + + H += [ render_template( "scodoc/help/modules.html", is_apc=is_apc, ue=ue, semestre_id=semestre_id, - ), + ) ] # cherche le numero adéquat (pour placer le module en fin de liste) modules = Matiere.query.get(1).modules.all() @@ -145,15 +155,43 @@ def module_create(matiere_id=None, module_type=None, semestre_id=None): "code", { "size": 10, - "explanation": "code du module (doit être unique dans la formation)", + "explanation": "code du module, ressource ou SAÉ. Exemple M1203, R2.01, ou SAÉ 3.4. Ce code doit être unique dans la formation.", "allow_null": False, "validator": lambda val, field, formation_id=ue.formation_id: check_module_code_unicity( val, field, formation_id ), }, ), - ("titre", {"size": 30, "explanation": "nom du module"}), - ("abbrev", {"size": 20, "explanation": "nom abrégé (pour bulletins)"}), + ( + "titre", + { + "size": 30, + "explanation": "nom du module. Exemple: Introduction à la démarche ergonomique", + }, + ), + ( + "abbrev", + { + "size": 20, + "explanation": "nom abrégé (pour les bulletins). Exemple: Intro. à l'ergonomie", + }, + ), + ] + semestres_indices = list(range(1, parcours.NB_SEM + 1)) + descr += [ + ( + "semestre_id", + { + "input_type": "menu", + "type": "int", + "title": parcours.SESSION_NAME.capitalize(), + "explanation": "%s du module" % parcours.SESSION_NAME, + "labels": [str(x) for x in semestres_indices], + "allowed_values": semestres_indices, + }, + ), + ] + descr += [ ( "module_type", { @@ -166,22 +204,29 @@ def module_create(matiere_id=None, module_type=None, semestre_id=None): ), ( "heures_cours", - {"size": 4, "type": "float", "explanation": "nombre d'heures de cours"}, + { + "title": "Heures de cours", + "size": 4, + "type": "float", + "explanation": "nombre d'heures de cours (optionnel)", + }, ), ( "heures_td", { + "title": "Heures de TD", "size": 4, "type": "float", - "explanation": "nombre d'heures de Travaux Dirigés", + "explanation": "nombre d'heures de Travaux Dirigés (optionnel)", }, ), ( "heures_tp", { + "title": "Heures de TP", "size": 4, "type": "float", - "explanation": "nombre d'heures de Travaux Pratiques", + "explanation": "nombre d'heures de Travaux Pratiques (optionnel)", }, ), ] @@ -198,7 +243,6 @@ def module_create(matiere_id=None, module_type=None, semestre_id=None): ), ] else: - semestres_indices = list(range(1, parcours.NB_SEM + 1)) descr += [ ( "coefficient", @@ -209,19 +253,8 @@ def module_create(matiere_id=None, module_type=None, semestre_id=None): "allow_null": False, }, ), - ( - "semestre_id", - { - "input_type": "menu", - "type": "int", - "title": parcours.SESSION_NAME.capitalize(), - "explanation": "%s de début du module dans la formation standard" - % parcours.SESSION_NAME, - "labels": [str(x) for x in semestres_indices], - "allowed_values": semestres_indices, - }, - ), ] + descr += [ # ('ects', { 'size' : 4, 'type' : 'float', 'title' : 'ECTS', 'explanation' : 'nombre de crédits ECTS (inutilisés: les crédits sont associés aux UE)' }), ("formation_id", {"default": ue.formation_id, "input_type": "hidden"}), diff --git a/app/scodoc/sco_edit_ue.py b/app/scodoc/sco_edit_ue.py index 77b89b1d0..121521e8f 100644 --- a/app/scodoc/sco_edit_ue.py +++ b/app/scodoc/sco_edit_ue.py @@ -29,7 +29,8 @@ """ import flask -from flask import g, url_for, request +from flask import url_for, render_template +from flask import g, request from flask_login import current_user from app.models.formations import Formation, UniteEns @@ -260,7 +261,7 @@ def ue_edit(ue_id=None, create=False, formation_id=None): { "input_type": "menu", "type": "int", - "allow_null": True, + "allow_null": False, "title": parcours.SESSION_NAME.capitalize(), "explanation": "%s de l'UE dans la formation" % parcours.SESSION_NAME, "labels": ["non spécifié"] + [str(x) for x in semestres_indices], @@ -324,7 +325,7 @@ def ue_edit(ue_id=None, create=False, formation_id=None): }, ), ] - if create and not parcours.UE_IS_MODULE: + if create and not parcours.UE_IS_MODULE and not is_apc: fw.append( ( "create_matiere", @@ -357,7 +358,9 @@ def ue_edit(ue_id=None, create=False, formation_id=None): formation_id, int(tf[2]["semestre_idx"]) ) ue_id = do_ue_create(tf[2]) - if parcours.UE_IS_MODULE or tf[2]["create_matiere"]: + if is_apc or parcours.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_id = sco_edit_matiere.do_matiere_create( {"ue_id": ue_id, "titre": tf[2]["titre"], "numero": 1}, ) @@ -446,7 +449,7 @@ def ue_delete(ue_id=None, delete_validations=False, dialog_confirmed=False): return do_ue_delete(ue_id, delete_validations=delete_validations) -def ue_table(formation_id=None, msg=""): # was ue_list +def ue_table(formation_id=None, semestre_idx=1, msg=""): # was ue_list """Liste des matières et modules d'une formation, avec liens pour éditer (si non verrouillée). """ @@ -459,7 +462,11 @@ def ue_table(formation_id=None, msg=""): # was ue_list parcours = formation.get_parcours() is_apc = parcours.APC_SAE locked = sco_formations.formation_has_locked_sems(formation_id) - + if semestre_idx == "all": + semestre_idx = None + else: + semestre_idx = int(semestre_idx) + semestre_ids = range(1, parcours.NB_SEM + 1) ues = ue_list(args={"formation_id": formation_id, "is_external": False}) ues_externes = ue_list(args={"formation_id": formation_id, "is_external": True}) # tri par semestre et numero: @@ -538,54 +545,33 @@ du programme" (menu "Semestre") si vous avez un semestre en cours); ) # Description de la formation - H.append('
') H.append( - f"""
Titre: - {formation.titre} -
-
Titre officiel: - {formation.titre_officiel} -
-
Acronyme: - {formation.acronyme} -
-
Code: - {formation.formation_code} -
-
Version: - {formation.version} -
-
Type parcours: - {parcours.__doc__} -
- """ + render_template( + "pn/form_descr.html", + formation=formation, + parcours=parcours, + editable=editable, + ) ) - if parcours.UE_IS_MODULE: - H.append( - """
- (Chaque module est une UE)
""" - ) - - if editable: - H.append( - f"""
modifier ces informations
""" - ) - H.append("
") # Formation APC (BUT) ? if is_apc: H.append( f"""
-
Formation par compétences (BUT)
+
Formation par compétences (BUT) + - Semestre {_html_select_semestre_idx(formation_id, semestre_ids, semestre_idx)} + +
+ """ + ) + H.append( + f""" -
""" + """ ) # Description des UE/matières/modules H.append( @@ -600,7 +586,10 @@ du programme" (menu "Semestre") si vous avez un semestre en cours); if is_apc: H.append( sco_edit_apc.html_edit_formation_apc( - formation, editable=editable, tag_editable=tag_editable + formation, + semestre_idx=semestre_idx, + editable=editable, + tag_editable=tag_editable, ) ) else: @@ -726,7 +715,7 @@ du programme" (menu "Semestre") si vous avez un semestre en cours);
  • Mettre en place un nouveau semestre de formation %(acronyme)s + }">Mettre en place un nouveau semestre de formation {formation.acronyme}
  • """ ) @@ -739,6 +728,30 @@ du programme" (menu "Semestre") si vous avez un semestre en cours); return "".join(H) +def _html_select_semestre_idx(formation_id, semestre_ids, semestre_idx): + htm = """
    Semestre: + + +
    """ + return htm + + def _ue_table_ues( parcours, ues, @@ -833,12 +846,8 @@ def _ue_table_ues( ) else: H.append('[verrouillé]') - if parcours.APC_SAE: - func_html_list = _ue_table_ressources_saes - else: - func_html_list = _ue_table_matieres H.append( - func_html_list( + _ue_table_matieres( parcours, ue, editable, @@ -848,7 +857,6 @@ def _ue_table_ues( arrow_none, delete_icon, delete_disabled_icon, - module_type=module_type, ) ) return "\n".join(H) @@ -864,7 +872,6 @@ def _ue_table_matieres( arrow_none, delete_icon, delete_disabled_icon, - module_type=None, ): """Édition de programme: liste des matières (et leurs modules) d'une UE.""" H = [] diff --git a/app/static/css/scodoc.css b/app/static/css/scodoc.css index e9ae3f073..5040be984 100644 --- a/app/static/css/scodoc.css +++ b/app/static/css/scodoc.css @@ -1485,20 +1485,22 @@ div.formation_list_ues_titre { padding-left: 24px; padding-right: 24px; font-size: 120%; + font-weight: bold; } -div.formation_list_modules { +div.formation_list_modules, div.formation_list_ues { border-radius: 18px; margin-left: 10px; margin-right: 10px; margin-bottom: 10px; padding-bottom: 1px; } -div.formation_list_modules_titre { - padding-left: 24px; - padding-right: 24px; - font-weight: bold; - font-size: 120%; +div.formation_list_ues { + background-color: #b7d2fa; + margin-top: 20px +} +div.formation_list_modules { + margin-top: 20px; } div.formation_list_modules_RESSOURCE { background-color: #f8c844; @@ -1509,12 +1511,25 @@ div.formation_list_modules_SAE { div.formation_list_modules_STANDARD { background-color: #afafc2; } +div.formation_list_modules_titre { + padding-left: 24px; + padding-right: 24px; + font-weight: bold; + font-size: 120%; +} +div.formation_list_ues ul.notes_module_list { + margin-top: 0px; + margin-bottom: -1px; + padding-top: 5px; + padding-bottom: 5px; +} div.formation_list_modules ul.notes_module_list { margin-top: 0px; margin-bottom: -1px; padding-top: 5px; padding-bottom: 5px; } + li.module_malus span.formation_module_tit { color: red; font-weight: bold; @@ -1530,14 +1545,23 @@ div.ue_list_tit { margin-top: 5px; } +ul.apc_ue_list { + background-color: rgba(180, 189, 191, 0.14); + margin-left: 8px; + margin-right: 8px; +} ul.notes_ue_list { - background-color: rgb(240,240,240); margin-top: 4px; margin-right: 1em; + margin-left: 1em; + padding-top: 1em; + padding-bottom: 1em; + font-weight: bold; } li.notes_ue_list { margin-top: 9px; + list-style-type: none; } span.ue_code { diff --git a/app/static/css/table_editor.css b/app/static/css/table_editor.css index 32e33f2ed..4a4bcf8b2 100644 --- a/app/static/css/table_editor.css +++ b/app/static/css/table_editor.css @@ -11,6 +11,9 @@ body { display: grid; grid-auto-rows: minmax(24px, auto); gap: 2px; + margin-top: 5px; + background: #fffefa; + margin: 10px; } .entete{ background: #09c; diff --git a/app/templates/pn/form_descr.html b/app/templates/pn/form_descr.html new file mode 100644 index 000000000..2d55cf532 --- /dev/null +++ b/app/templates/pn/form_descr.html @@ -0,0 +1,36 @@ +{# Chapeau description d'une formation #} + +
    +
    Titre: + {{formation.titre}} +
    +
    Titre officiel: + {{formation.titre_officiel}} +
    +
    Acronyme: + {{formation.acronyme}} +
    +
    Code: + {{formation.formation_code}} +
    +
    Version: + {{formation.version}} +
    +
    Type parcours: + {{parcours.__doc__}} +
    + + {% if parcours.UE_IS_MODULE %} +
    + (Chaque module est une UE) +
    + {% endif %} + + {% if editable %} +
    modifier ces informations +
    + {% endif %} +
    \ No newline at end of file diff --git a/app/templates/pn/form_mods.html b/app/templates/pn/form_mods.html index 830c3f672..1b54b1ccf 100644 --- a/app/templates/pn/form_mods.html +++ b/app/templates/pn/form_mods.html @@ -14,24 +14,24 @@ {% if editable and not loop.first %} {{arrow_up|safe}} + }}" class="aud">{{icons.arrow_up|safe}} {% else %} - {{arrow_none|safe}} + {{icons.arrow_none|safe}} {% endif %} {% if editable and not loop.last %} {{arrow_down|safe}} + }}" class="aud">{{icons.arrow_down|safe}} {% else %} - {{arrow_none|safe}} + {{icons.arrow_none|safe}} {% endif %} {% if editable and not mod.modimpls.count() %} {{delete_icon|safe}} + }}">{{icons.delete|safe}} {% else %} - {{delete_disabled_icon|safe}} + {{icons.delete_disabled|safe}} {% endif %} {% if editable %} @@ -48,7 +48,7 @@ {{formation.get_parcours().SESSION_NAME}} {{mod.semestre_id}} - ({{mod.heures_cours}}/{{mod.heures_td}}/{{mod.heures_tp}}, + ({{mod.heures_cours|default(" ",true)|safe}}/{{mod.heures_td|default(" ",true)|safe}}/{{mod.heures_tp|default(" ",true)|safe}}, Apo:
    + class="{% if tag_editable %}module_tag_editor{% else %}module_tag_editor_ro{% endif %}">{{mod.tags|join(', ', attribute='title')}}
    {% endfor %} - {% if editable %} + {% if editable and formation.ues.count() and formation.ues[0].matieres.count() %}
  • {{create_element_msg}}
  • diff --git a/app/templates/pn/form_modules_ue_coefs.html b/app/templates/pn/form_modules_ue_coefs.html index a93db230b..1d63131c3 100644 --- a/app/templates/pn/form_modules_ue_coefs.html +++ b/app/templates/pn/form_modules_ue_coefs.html @@ -1,94 +1,84 @@ - - - - - - - - - - - Édition coef. formation - - - -

    Formation {{formation.titre}} ({{formation.acronyme}}) - [version {{formation.version}}] code {{formation.code}}

    - -
    Semestre: - {% for i in semestre_ids %} - + {% endfor %} -
    + revenir à la formation + -
    +
    - - - - \ No newline at end of file + }); + function save(obj) { + var value = obj.innerText.trim(); + if (value.length == 0) { + value = "0"; + } + if (!/^[\d.,]+$/.test(value)) { + message("Il est attendu un nombre"); + return false; + } + if (value == obj.dataset.data) { + return true; // Aucune modification, pas d'enregistrement mais on continue normalement + } + obj.dataset.data = value; + obj.classList.add("wait"); + // XXX DEBUG + // console.log(` + // x : ${getComputedStyle(obj).getPropertyValue("--x")} + // y : ${getComputedStyle(obj).getPropertyValue("--y")} + // data : ${value} + // ue_id: ${obj.dataset.ue_id} + // module_id : ${obj.dataset.module_id} + // `); + $.post("{{data_save}}", + { + module_id: obj.dataset.module_id, + ue_id: obj.dataset.ue_id, + coef: value + }, + function (result) { + obj.classList.remove("wait"); + if (obj.dataset.orig != value) + obj.classList.add("modified"); + else + obj.classList.remove("modified"); + // Lorsque les données sont bien enregistrées, on enlève + // l'indication que c'est bon au bout d'un temps + //setTimeout(() => { + // obj.classList.remove("modified"); + //}, 1000); + } + ); + return true; + } + \ No newline at end of file diff --git a/app/templates/pn/form_ues.html b/app/templates/pn/form_ues.html index ac0486446..8b5172da1 100644 --- a/app/templates/pn/form_ues.html +++ b/app/templates/pn/form_ues.html @@ -1,49 +1,54 @@ {# Édition liste UEs APC #}
    Unités d'Enseignement (UEs)
    - {% endif %} -
    \ No newline at end of file diff --git a/app/views/notes.py b/app/views/notes.py index 1b1c97dd9..7423f1f8a 100644 --- a/app/views/notes.py +++ b/app/views/notes.py @@ -344,8 +344,10 @@ sco_publish( @scodoc @permission_required(Permission.ScoView) @scodoc7func -def ue_table(formation_id=None, msg=""): - return sco_edit_ue.ue_table(formation_id=formation_id, msg=msg) +def ue_table(formation_id=None, semestre_idx=1, msg=""): + return sco_edit_ue.ue_table( + formation_id=formation_id, semestre_idx=semestre_idx, msg=msg + ) @bp.route("/ue_set_internal", methods=["GET", "POST"]) diff --git a/app/views/pn_modules.py b/app/views/pn_modules.py index 0eb0d46e4..a40f6f1e0 100644 --- a/app/views/pn_modules.py +++ b/app/views/pn_modules.py @@ -53,6 +53,7 @@ from app.views import notes_bp as bp from app.scodoc import sco_utils as scu from app.scodoc import notesdb as ndb +from app.scodoc import sco_formations from app import log from app.models.formations import Formation, UniteEns, Module @@ -159,19 +160,41 @@ def edit_modules_ue_coefs(formation_id, semestre_idx=None): formation = models.Formation.query.filter_by( formation_id=formation_id ).first_or_404() - return render_template( - "pn/form_modules_ue_coefs.html", - formation=formation, - data_source=url_for( - "notes.table_modules_ue_coefs", - scodoc_dept=g.scodoc_dept, - formation_id=formation_id, - semestre_idx=semestre_idx or "", + locked = sco_formations.formation_has_locked_sems(formation_id) + if locked: + lockicon = scu.icontag("lock32_img", title="verrouillé") + else: + lockicon = "" + H = [ + html_sco_header.sco_header( + cssstyles=["css/table_editor.css"], + javascripts=[ + "js/table_editor.js", + ], + page_title=f"Coefs programme {formation.acronyme}", ), - data_save=url_for( - "notes.set_module_ue_coef", - scodoc_dept=g.scodoc_dept, + f"""

    Formation {formation.titre} ({formation.acronyme}) + [version {formation.version}] code {formation.formation_code} + {lockicon} +

    + """, + render_template( + "pn/form_modules_ue_coefs.html", + formation=formation, + data_source=url_for( + "notes.table_modules_ue_coefs", + scodoc_dept=g.scodoc_dept, + formation_id=formation_id, + semestre_idx=semestre_idx or "", + ), + data_save=url_for( + "notes.set_module_ue_coef", + scodoc_dept=g.scodoc_dept, + ), + semestre_idx=int(semestre_idx), + semestre_ids=range(1, formation.get_parcours().NB_SEM + 1), ), - semestre_idx=int(semestre_idx), - semestre_ids=range(1, formation.get_parcours().NB_SEM + 1), - ) + html_sco_header.sco_footer(), + ] + + return "\n".join(H)