From 47e40a9634f017484aabc11f88e9feaa52770d97 Mon Sep 17 00:00:00 2001
From: Emmanuel Viennet <emmanuel.viennet@gmail.com>
Date: Fri, 16 Aug 2024 01:11:06 +0200
Subject: [PATCH] Templatification: remplacement html_sem_header

---
 app/but/jury_but_pv.py                      |  2 +-
 app/models/events.py                        | 15 +++-
 app/scodoc/html_sco_header.py               | 15 ----
 app/scodoc/sco_archives_formsemestre.py     | 25 ++++--
 app/scodoc/sco_debouche.py                  |  1 -
 app/scodoc/sco_etape_apogee_view.py         |  2 -
 app/scodoc/sco_evaluation_check_abs.py      | 39 +++++----
 app/scodoc/sco_evaluation_recap.py          | 27 ++++--
 app/scodoc/sco_evaluations.py               | 27 +++---
 app/scodoc/sco_export_results.py            |  7 +-
 app/scodoc/sco_find_etud.py                 | 14 ++-
 app/scodoc/sco_formsemestre_custommenu.py   | 17 ++--
 app/scodoc/sco_formsemestre_edit.py         | 90 ++++++++++---------
 app/scodoc/sco_formsemestre_inscriptions.py | 44 ++++++----
 app/scodoc/sco_formsemestre_status.py       |  7 +-
 app/scodoc/sco_formsemestre_validation.py   | 19 ++--
 app/scodoc/sco_groups_view.py               |  9 +-
 app/scodoc/sco_inscr_passage.py             | 19 ++--
 app/scodoc/sco_lycee.py                     |  5 +-
 app/scodoc/sco_moduleimpl_inscriptions.py   | 21 ++---
 app/scodoc/sco_poursuite_dut.py             |  1 -
 app/scodoc/sco_preferences.py               | 29 +++----
 app/scodoc/sco_pv_forms.py                  | 70 ++++++++-------
 app/scodoc/sco_recapcomplet.py              |  2 +-
 app/scodoc/sco_report.py                    |  1 -
 app/scodoc/sco_synchro_etuds.py             |  7 +-
 app/scodoc/sco_ue_external.py               | 39 +++++----
 app/scodoc/sco_users.py                     | 11 ++-
 app/static/css/scodoc.css                   |  5 --
 app/templates/sco_page.j2                   | 19 ++--
 app/views/absences.py                       |  1 -
 app/views/notes.py                          | 96 +++++++++++----------
 app/views/scolar.py                         | 27 +++---
 33 files changed, 367 insertions(+), 346 deletions(-)

diff --git a/app/but/jury_but_pv.py b/app/but/jury_but_pv.py
index 1462f1fb..cdb46acd 100644
--- a/app/but/jury_but_pv.py
+++ b/app/but/jury_but_pv.py
@@ -105,7 +105,7 @@ def pvjury_page_but(formsemestre_id: int, fmt="html"):
         },
         xls_style_base=xls_style_base,
     )
-    return tab.make_page(fmt=fmt, javascripts=["js/etud_info.js"])
+    return tab.make_page(fmt=fmt)
 
 
 def pvjury_table_but(
diff --git a/app/models/events.py b/app/models/events.py
index c65ea0ee..3bc853e1 100644
--- a/app/models/events.py
+++ b/app/models/events.py
@@ -271,10 +271,10 @@ class ScolarNews(db.Model):
             return ""
         dept_news_url = url_for("scolar.dept_news", scodoc_dept=g.scodoc_dept)
         H = [
-            f"""<div class="scobox news"><div class="scobox-title"><a href="{
+            f"""<div class="scobox news"><div class="scobox-title" desktop="true"><a href="{
                 dept_news_url
             }">Dernières opérations</a>
-            </div><ul class="newslist">"""
+            </div><ul class="newslist" desktop="true">"""
         ]
 
         for news in news_list:
@@ -289,11 +289,18 @@ class ScolarNews(db.Model):
             </li>"""
         )
 
-        H.append("</ul></div>")
+        H.append(
+            """</ul>
+                 <ul class="newslist" mobile="true" style="margin-bottom: 0px;">
+                 <li><a href="{dept_news_url}" class="stdlink">Dernières opérations</a></li>
+                 </ul>
+                 </div>
+            """
+        )
 
         # Informations générales
         H.append(
-            f"""<div>
+            f"""<div desktop="true">
         Pour en savoir plus sur ScoDoc voir
         <a class="stdlink" href="{scu.SCO_ANNONCES_WEBSITE}">scodoc.org</a>
         </div>
diff --git a/app/scodoc/html_sco_header.py b/app/scodoc/html_sco_header.py
index 28d6249b..e152be10 100644
--- a/app/scodoc/html_sco_header.py
+++ b/app/scodoc/html_sco_header.py
@@ -242,18 +242,3 @@ def sco_footer():
         + scu.CUSTOM_HTML_FOOTER
         + """</body></html>"""
     )
-
-
-def html_sem_header(
-    title, with_page_header=True, with_h2=True, page_title=None, **args
-):
-    "Titre d'une page semestre avec lien vers tableau de bord"
-    # sem now unused and thus optional...
-    if with_page_header:
-        h = sco_header(page_title="%s" % (page_title or title), **args)
-    else:
-        h = ""
-    if with_h2:
-        return h + f"""<h2 class="formsemestre">{title}</h2>"""
-    else:
-        return h
diff --git a/app/scodoc/sco_archives_formsemestre.py b/app/scodoc/sco_archives_formsemestre.py
index cbcbd201..44986253 100644
--- a/app/scodoc/sco_archives_formsemestre.py
+++ b/app/scodoc/sco_archives_formsemestre.py
@@ -27,7 +27,7 @@
 import json
 
 import flask
-from flask import flash, g, request, url_for
+from flask import flash, g, render_template, request, url_for
 
 from app import ScoDocJSONEncoder
 from app.but import jury_but_pv
@@ -238,11 +238,6 @@ def formsemestre_archive(formsemestre_id, group_ids: list[int] = None):
     )
 
     H = [
-        html_sco_header.html_sem_header(
-            "Archiver les PV et résultats du semestre",
-            javascripts=sco_groups_view.JAVASCRIPTS,
-            cssstyles=sco_groups_view.CSSSTYLES,
-        ),
         """<p class="help">Cette page permet de générer et d'archiver tous
 les documents résultant de ce semestre: PV de jury, lettres individuelles,
 tableaux récapitulatifs.</p><p class="help">Les documents archivés sont
@@ -311,7 +306,17 @@ enregistrés et non modifiables, on peut les retrouver ultérieurement.
         html_foot_markup=menu_choix_groupe,
     )
     if tf[0] == 0:
-        return "\n".join(H) + "\n" + tf[1] + "\n".join(F)
+        return render_template(
+            "sco_page.j2",
+            title="Archiver les PV et résultats",
+            javascripts=sco_groups_view.JAVASCRIPTS,
+            cssstyles=sco_groups_view.CSSSTYLES,
+            content="<h2>Archiver les PV et résultats du semestre</h2>"
+            + "\n".join(H)
+            + "\n"
+            + tf[1]
+            + "\n".join(F),
+        )
     elif tf[0] == -1:
         msg = "Opération annulée"
     else:
@@ -371,7 +376,7 @@ def formsemestre_list_archives(formsemestre_id):
         }
         archives_descr.append(a)
 
-    H = [html_sco_header.html_sem_header("Archive des PV et résultats ")]
+    H = []
     if not archives_descr:
         H.append("<p>aucune archive enregistrée</p>")
     else:
@@ -399,7 +404,9 @@ def formsemestre_list_archives(formsemestre_id):
             H.append("</ul></li>")
         H.append("</ul>")
 
-    return "\n".join(H) + html_sco_header.sco_footer()
+    return render_template(
+        "sco_page.j2", title="Archive des PV et résultats", content="\n".join(H)
+    )
 
 
 def formsemestre_get_archived_file(formsemestre_id, archive_name, filename):
diff --git a/app/scodoc/sco_debouche.py b/app/scodoc/sco_debouche.py
index a0c9d7a5..116c815d 100644
--- a/app/scodoc/sco_debouche.py
+++ b/app/scodoc/sco_debouche.py
@@ -76,7 +76,6 @@ def report_debouche_date(start_year=None, fmt="html"):
     tab.base_url = f"{request.base_url}?start_year={start_year}"
     return tab.make_page(
         title="""<h2 class="formsemestre">Débouchés étudiants </h2>""",
-        javascripts=["js/etud_info.js"],
         fmt=fmt,
         with_html_headers=True,
     )
diff --git a/app/scodoc/sco_etape_apogee_view.py b/app/scodoc/sco_etape_apogee_view.py
index 9a576be8..1bf9cdf1 100644
--- a/app/scodoc/sco_etape_apogee_view.py
+++ b/app/scodoc/sco_etape_apogee_view.py
@@ -597,7 +597,6 @@ def _view_etuds_page(
     return f"""
         {html_sco_header.sco_header(
             page_title=title,
-            javascripts=["js/etud_info.js"],
         )}
         <h2>{title}</h2>
 
@@ -750,7 +749,6 @@ def view_apo_csv(etape_apo="", semset_id="", fmt="html"):
     H = [
         html_sco_header.sco_header(
             page_title=f"""Maquette Apogée enregistrée pour {etape_apo}""",
-            javascripts=["js/etud_info.js"],
         ),
         f"""<h2>Étudiants dans la maquette Apogée {etape_apo}</h2>
         <p>Pour l'ensemble <a class="stdlink" href="{
diff --git a/app/scodoc/sco_evaluation_check_abs.py b/app/scodoc/sco_evaluation_check_abs.py
index 852309ab..e8865708 100644
--- a/app/scodoc/sco_evaluation_check_abs.py
+++ b/app/scodoc/sco_evaluation_check_abs.py
@@ -27,7 +27,7 @@
 
 """Vérification des absences à une évaluation
 """
-from flask import url_for, g
+from flask import g, render_template, url_for
 from flask_sqlalchemy.query import Query
 
 from app import db
@@ -37,6 +37,7 @@ from app.scodoc import html_sco_header
 from app.scodoc import sco_evaluations
 from app.scodoc import sco_evaluation_db
 from app.scodoc import sco_groups
+from app.views import ScoData
 
 
 def evaluation_check_absences(evaluation: Evaluation):
@@ -109,8 +110,10 @@ def evaluation_check_absences(evaluation: Evaluation):
 
 def evaluation_check_absences_html(
     evaluation: Evaluation, with_header=True, show_ok=True
-):
-    """Affiche état vérification absences d'une évaluation"""
+) -> str:
+    """Affiche état vérification absences d'une évaluation.
+    Si with_header, génère page complète, sinon fragment html.
+    """
     (
         note_but_abs,  # une note alors qu'il était signalé abs
         abs_non_signalee,  # note ABS alors que pas signalé abs
@@ -121,10 +124,6 @@ def evaluation_check_absences_html(
 
     if with_header:
         H = [
-            html_sco_header.html_sem_header(
-                "Vérification absences à l'évaluation",
-                formsemestre_id=evaluation.moduleimpl.formsemestre_id,
-            ),
             sco_evaluations.evaluation_describe(evaluation_id=evaluation.id),
             """<p class="help">Vérification de la cohérence entre les notes saisies
             et les absences signalées.</p>""",
@@ -208,19 +207,19 @@ def evaluation_check_absences_html(
         etudlist(abs_but_exc)
 
     if with_header:
-        H.append(html_sco_header.sco_footer())
+        return render_template(
+            "sco_page.j2",
+            title="Vérification absences à l'évaluation",
+            content="<h2>Vérification absences à l'évaluation</h2>" + "\n".join(H),
+            sco=ScoData(formsemestre=evaluation.moduleimpl.formsemestre),
+        )
     return "\n".join(H)
 
 
-def formsemestre_check_absences_html(formsemestre_id):
-    """Affiche etat verification absences pour toutes les evaluations du semestre !"""
-    formsemestre: FormSemestre = FormSemestre.query.filter_by(
-        dept_id=g.scodoc_dept_id, id=formsemestre_id
-    ).first_or_404()
+def formsemestre_check_absences_html(formsemestre_id: int):
+    """Affiche état vérification absences pour toutes les évaluations du semestre."""
+    formsemestre = FormSemestre.get_formsemestre(formsemestre_id)
     H = [
-        html_sco_header.html_sem_header(
-            "Vérification absences aux évaluations de ce semestre",
-        ),
         """<p class="help">Vérification de la cohérence entre les notes saisies
             et les absences signalées.
           Sont listés tous les modules avec des évaluations.<br>Aucune action n'est effectuée:
@@ -248,5 +247,9 @@ def formsemestre_check_absences_html(formsemestre_id):
                 )
             H.append("</div>")
 
-    H.append(html_sco_header.sco_footer())
-    return "\n".join(H)
+    return render_template(
+        "sco_page.j2",
+        content="<h2>Vérification absences aux évaluations de ce semestre</h2>"
+        + "\n".join(H),
+        title="Vérification absences aux évaluations de ce semestre",
+    )
diff --git a/app/scodoc/sco_evaluation_recap.py b/app/scodoc/sco_evaluation_recap.py
index 2648c33d..e9edd611 100644
--- a/app/scodoc/sco_evaluation_recap.py
+++ b/app/scodoc/sco_evaluation_recap.py
@@ -10,7 +10,7 @@ avec leur état.
 Sur une idée de Pascal Bouron, de Lyon.
 """
 import time
-from flask import g, url_for
+from flask import g, render_template, url_for
 
 from app import db
 from app.models import Evaluation, FormSemestre
@@ -23,7 +23,7 @@ import app.scodoc.sco_utils as scu
 
 def evaluations_recap(formsemestre_id: int) -> str:
     """Page récap. de toutes les évaluations d'un semestre"""
-    formsemestre: FormSemestre = FormSemestre.query.get_or_404(formsemestre_id)
+    formsemestre = FormSemestre.get_formsemestre(formsemestre_id)
     rows, titles = evaluations_recap_table(formsemestre)
     column_ids = titles.keys()
     filename = scu.sanitize_filename(
@@ -32,11 +32,14 @@ def evaluations_recap(formsemestre_id: int) -> str:
     if not rows:
         return '<div class="evaluations_recap"><div class="message">aucune évaluation</div></div>'
     H = [
-        html_sco_header.sco_header(
-            page_title="Évaluations du semestre",
-            javascripts=["js/evaluations_recap.js"],
-        ),
-        f"""<div class="evaluations_recap"><table class="evaluations_recap compact {
+        f"""<h2>Évaluations du semestre</h2>
+        <div>
+            <a class="stdlink" href="{url_for(
+                'notes.formsemestre_check_absences_html',
+                scodoc_dept=g.scodoc_dept, formsemestre_id=formsemestre.id,
+                )}">Vérifier les absences aux évaluations</a>
+        </div>
+        <div class="evaluations_recap"><table class="evaluations_recap compact {
             'apc' if formsemestre.formation.is_apc() else 'classic'
             }"
             data-filename="{filename}">""",
@@ -56,13 +59,19 @@ def evaluations_recap(formsemestre_id: int) -> str:
 
     H.append(
         """</tbody></table></div>
-    <div class="help">Les étudiants démissionnaires ou défaillants ne sont pas pris en compte dans cette table.</div>
+    <div class="help">Les étudiants démissionnaires ou défaillants ne sont
+    pas pris en compte dans cette table.</div>
     """
     )
     H.append(
         html_sco_header.sco_footer(),
     )
-    return "".join(H)
+    return render_template(
+        "sco_page.j2",
+        title="Évaluations du semestre",
+        javascripts=["js/evaluations_recap.js"],
+        content="".join(H),
+    )
 
 
 def evaluations_recap_table(formsemestre: FormSemestre) -> list[dict]:
diff --git a/app/scodoc/sco_evaluations.py b/app/scodoc/sco_evaluations.py
index 3fdef7e0..a961ecb3 100644
--- a/app/scodoc/sco_evaluations.py
+++ b/app/scodoc/sco_evaluations.py
@@ -31,9 +31,7 @@ import collections
 import datetime
 import operator
 
-from flask import url_for
-from flask import g
-from flask import request
+from flask import g, render_template, request, url_for
 from flask_login import current_user
 
 from app import db
@@ -45,8 +43,6 @@ from app.models import Evaluation, FormSemestre, ModuleImpl, Module
 import app.scodoc.sco_utils as scu
 from app.scodoc.sco_utils import ModuleType
 from app.scodoc.gen_tables import GenTable
-from app.scodoc import html_sco_header
-from app.scodoc import sco_cal
 from app.scodoc import sco_evaluation_db
 from app.scodoc import sco_formsemestre_inscriptions
 from app.scodoc import sco_groups
@@ -470,7 +466,7 @@ class CalendrierEval(sco_gen_cal.Calendrier):
 
 # View
 def formsemestre_evaluations_cal(formsemestre_id):
-    """Page avec calendrier de toutes les evaluations de ce semestre"""
+    """Page avec calendrier de toutes les évaluations de ce semestre"""
     formsemestre = FormSemestre.get_formsemestre(formsemestre_id)
     nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
 
@@ -481,13 +477,12 @@ def formsemestre_evaluations_cal(formsemestre_id):
     cal = CalendrierEval(year, evaluations, nt)
     cal_html = cal.get_html()
 
-    return f"""
-    {
-        html_sco_header.html_sem_header(
-            "Evaluations du semestre",
-            cssstyles=["css/calabs.css"],
-        )
-    }
+    return render_template(
+        "sco_page.j2",
+        cssstyles=["css/calabs.css"],
+        title="Évaluations du semestre",
+        content=f"""
+    <h2>Évaluations du semestre</h2>
     <div class="cal_evaluations">
     { cal_html }
     </div>
@@ -513,8 +508,8 @@ def formsemestre_evaluations_cal(formsemestre_id):
         )
         }" class="stdlink">voir les délais de correction</a>
     </p>
-    { html_sco_header.sco_footer() }
-    """
+    """,
+    )
 
 
 def evaluation_date_first_completion(evaluation_id) -> datetime.datetime:
@@ -651,7 +646,7 @@ def evaluation_describe(evaluation_id="", edit_in_place=True, link_saisie=True)
     """HTML description of evaluation, for page headers
     edit_in_place: allow in-place editing when permitted (not implemented)
     """
-    evaluation: Evaluation = Evaluation.query.get_or_404(evaluation_id)
+    evaluation = Evaluation.get_evaluation(evaluation_id)
     modimpl = evaluation.moduleimpl
     responsable: User = db.session.get(User, modimpl.responsable_id)
     resp_nomprenom = responsable.get_prenomnom()
diff --git a/app/scodoc/sco_export_results.py b/app/scodoc/sco_export_results.py
index aaa707ab..252e7f16 100644
--- a/app/scodoc/sco_export_results.py
+++ b/app/scodoc/sco_export_results.py
@@ -73,7 +73,9 @@ def _build_results_table(start_date=None, end_date=None, types_parcours=[]):
     formsemestre_ids_parcours = [sem["formsemestre_id"] for sem in semlist_parcours]
 
     # Ensemble des étudiants
-    etuds_infos = {}  # etudid : { formsemestre_id d'inscription le plus recent dans les dates considérées, etud }
+    etuds_infos = (
+        {}
+    )  # etudid : { formsemestre_id d'inscription le plus recent dans les dates considérées, etud }
     for formsemestre_id in formsemestre_ids_parcours:
         formsemestre = FormSemestre.get_formsemestre(formsemestre_id)
         nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
@@ -285,8 +287,7 @@ def scodoc_table_results(
     H = [
         html_sco_header.sco_header(
             page_title="Export résultats",
-            javascripts=html_sco_header.BOOTSTRAP_JS
-            + ["js/etud_info.js", "js/export_results.js"],
+            javascripts=html_sco_header.BOOTSTRAP_JS + ["js/export_results.js"],
             cssstyles=html_sco_header.BOOTSTRAP_CSS,
         ),
         # XXX
diff --git a/app/scodoc/sco_find_etud.py b/app/scodoc/sco_find_etud.py
index ecf97dc9..eb3fb169 100644
--- a/app/scodoc/sco_find_etud.py
+++ b/app/scodoc/sco_find_etud.py
@@ -28,7 +28,7 @@
 """Recherche d'étudiants
 """
 import flask
-from flask import url_for, g, request
+from flask import url_for, g, render_template, request
 from flask_login import current_user
 import sqlalchemy as sa
 
@@ -177,13 +177,7 @@ def search_etud_in_dept(expnom=""):
         url_args["etudid"] = etuds[0].id
         return flask.redirect(url_for(endpoint, **url_args))
 
-    H = [
-        html_sco_header.sco_header(
-            page_title="Recherche d'un étudiant",
-            no_sidebar=False,
-            javascripts=["js/etud_info.js"],
-        )
-    ]
+    H = []
     if len(etuds) == 0 and len(etuds) <= 1:
         H.append("""<h2>chercher un étudiant:</h2>""")
     else:
@@ -266,7 +260,9 @@ def search_etud_in_dept(expnom=""):
         """<p class="help">La recherche porte sur tout ou partie du NOM ou du NIP
         de l'étudiant. Saisir au moins deux caractères.</p>"""
     )
-    return "\n".join(H) + html_sco_header.sco_footer()
+    return render_template(
+        "sco_page_dept.j2", title="Recherche d'un étudiant", content="\n".join(H)
+    )
 
 
 def search_etuds_infos(expnom=None, code_nip=None) -> list[dict]:
diff --git a/app/scodoc/sco_formsemestre_custommenu.py b/app/scodoc/sco_formsemestre_custommenu.py
index 3be63831..d719c42d 100644
--- a/app/scodoc/sco_formsemestre_custommenu.py
+++ b/app/scodoc/sco_formsemestre_custommenu.py
@@ -28,7 +28,7 @@
 """Menu "custom" (défini par l'utilisateur) dans les semestres
 """
 import flask
-from flask import g, url_for, request
+from flask import g, url_for, render_template, request
 from flask_login import current_user
 
 from app.models.config import ScoDocSiteConfig, PersonalizedLink
@@ -37,9 +37,6 @@ import app.scodoc.sco_utils as scu
 import app.scodoc.notesdb as ndb
 from app.scodoc.TrivialFormulator import TrivialFormulator
 from app.scodoc import html_sco_header
-from app.scodoc import htmlutils
-from app.scodoc import sco_formsemestre
-from app.scodoc import sco_edt_cal
 
 _custommenuEditor = ndb.EditableTable(
     "notes_formsemestre_custommenu",
@@ -106,14 +103,13 @@ def formsemestre_custommenu_html(formsemestre_id):
 
 def formsemestre_custommenu_edit(formsemestre_id):
     """Dialog to edit the custom menu"""
-    formsemestre: FormSemestre = FormSemestre.query.get_or_404(formsemestre_id)
+    formsemestre = FormSemestre.get_formsemestre(formsemestre_id)
     dest_url = url_for(
         "notes.formsemestre_status",
         scodoc_dept=g.scodoc_dept,
         formsemestre_id=formsemestre_id,
     )
     H = [
-        html_sco_header.html_sem_header("Modification du menu du semestre "),
         """<div class="help">
         <p>Ce menu, spécifique à chaque semestre, peut être utilisé pour
         placer des liens vers vos applications préférées.
@@ -164,7 +160,14 @@ def formsemestre_custommenu_edit(formsemestre_id):
         name="tf",
     )
     if tf[0] == 0:
-        return "\n".join(H) + "\n" + tf[1] + html_sco_header.sco_footer()
+        return render_template(
+            "sco_page.j2",
+            title="Modification du menu du semestre",
+            content="<h2>Modification du menu du semestre</h2>"
+            + "\n".join(H)
+            + "\n"
+            + tf[1],
+        )
     elif tf[0] == -1:
         return flask.redirect(dest_url)
     else:
diff --git a/app/scodoc/sco_formsemestre_edit.py b/app/scodoc/sco_formsemestre_edit.py
index e5744970..d5705b30 100644
--- a/app/scodoc/sco_formsemestre_edit.py
+++ b/app/scodoc/sco_formsemestre_edit.py
@@ -28,8 +28,7 @@
 """Form choix modules / responsables et creation formsemestre
 """
 import flask
-from flask import url_for, flash, redirect
-from flask import g, request
+from flask import flash, g, request, redirect, render_template, url_for
 from flask_login import current_user
 import sqlalchemy as sa
 
@@ -97,18 +96,10 @@ def formsemestre_createwithmodules():
     return "\n".join(H) + html_sco_header.sco_footer()
 
 
-def formsemestre_editwithmodules(formsemestre_id):
+def formsemestre_editwithmodules(formsemestre_id: int):
     """Page modification semestre"""
-    formsemestre: FormSemestre = FormSemestre.query.filter_by(
-        id=formsemestre_id, dept_id=g.scodoc_dept_id
-    ).first_or_404()
-    H = [
-        html_sco_header.html_sem_header(
-            "Modification du semestre",
-            javascripts=["libjs/AutoSuggest.js", "js/formsemestre_edit.js"],
-            cssstyles=["css/autosuggest_inquisitor.css"],
-        )
-    ]
+    formsemestre = FormSemestre.get_formsemestre(formsemestre_id)
+    H = []
     if not formsemestre.etat:
         H.append(
             f"""<p>{scu.icontag(
@@ -136,7 +127,13 @@ def formsemestre_editwithmodules(formsemestre_id):
             """
             )
 
-    return "\n".join(H) + html_sco_header.sco_footer()
+    return render_template(
+        "sco_page.j2",
+        javascripts=["libjs/AutoSuggest.js", "js/formsemestre_edit.js"],
+        cssstyles=["css/autosuggest_inquisitor.css"],
+        title="Modification du semestre",
+        content="<h2>Modification du semestre</h2>" + "\n".join(H),
+    )
 
 
 def can_edit_sem(formsemestre_id: int = None, sem=None):
@@ -1162,12 +1159,9 @@ def formsemestre_clone(formsemestre_id):
     }
 
     H = [
-        html_sco_header.html_sem_header(
-            "Copie du semestre",
-            javascripts=["libjs/AutoSuggest.js"],
-            cssstyles=["css/autosuggest_inquisitor.css"],
-        ),
-        """<p class="help">Cette opération duplique un semestre: on reprend les mêmes modules et responsables. Aucun étudiant n'est inscrit.</p>""",
+        """<p class="help">Cette opération duplique un semestre:
+        on reprend les mêmes modules et responsables.
+        Aucun étudiant n'est inscrit.</p>""",
     ]
 
     descr = [
@@ -1244,7 +1238,13 @@ def formsemestre_clone(formsemestre_id):
         if ndb.DateDMYtoISO(tf[2]["date_debut"]) > ndb.DateDMYtoISO(tf[2]["date_fin"]):
             msg = '<ul class="tf-msg"><li class="tf-msg">Dates de début et fin incompatibles !</li></ul>'
     if tf[0] == 0 or msg:
-        return "".join(H) + msg + tf[1] + html_sco_header.sco_footer()
+        return render_template(
+            "sco_page.j2",
+            javascripts=["libjs/AutoSuggest.js"],
+            cssstyles=["css/autosuggest_inquisitor.css"],
+            title="Copie du semestre",
+            content="".join(H) + msg + tf[1],
+        )
     elif tf[0] == -1:  # cancel
         return flask.redirect(
             url_for(
@@ -1388,8 +1388,8 @@ def formsemestre_delete(formsemestre_id: int) -> str | flask.Response:
     """Delete a formsemestre (affiche avertissements)"""
     formsemestre: FormSemestre = FormSemestre.get_formsemestre(formsemestre_id)
     H = [
-        html_sco_header.html_sem_header("Suppression du semestre"),
-        """<div class="ue_warning"><span>Attention !</span>
+        """<h2>Suppression du semestre</h2>
+<div class="ue_warning"><span>Attention !</span>
 <p class="help">A n'utiliser qu'en cas d'erreur lors de la saisie d'une formation. Normalement,
 <b>un semestre ne doit jamais être supprimé</b>
 (on perd la mémoire des notes et de tous les événements liés à ce semestre !).
@@ -1442,8 +1442,9 @@ Ceci n'est possible que si :
             )
         else:
             H.append(tf[1])
-
-        return "\n".join(H) + html_sco_header.sco_footer()
+        return render_template(
+            "sco_page.j2", title="Suppression du semestre", content="\n".join(H)
+        )
 
     if tf[0] == -1:  # cancel
         return flask.redirect(
@@ -1743,7 +1744,6 @@ def formsemestre_edit_uecoefs(formsemestre_id, err_ue_id=None):
     if not ok:
         return err
 
-    footer = html_sco_header.sco_footer()
     help_msg = """<p class="help">
     Seuls les modules ont un coefficient. Cependant, il est nécessaire d'affecter un coefficient aux UE capitalisée pour pouvoir les prendre en compte dans la moyenne générale.
     </p>
@@ -1766,7 +1766,6 @@ def formsemestre_edit_uecoefs(formsemestre_id, err_ue_id=None):
     </p>
     """
     H = [
-        html_sco_header.html_sem_header("Coefficients des UE du semestre"),
         help_msg,
     ]
     #
@@ -1809,7 +1808,11 @@ def formsemestre_edit_uecoefs(formsemestre_id, err_ue_id=None):
         initvalues=initvalues,
     )
     if tf[0] == 0:
-        return "\n".join(H) + tf[1] + footer
+        return render_template(
+            "sco_page.j2",
+            title="Coefficients des UE du semestre",
+            content="<h2>Coefficients des UE du semestre</h2>" + "\n".join(H) + tf[1],
+        )
     elif tf[0] == -1:
         return redirect(
             url_for(
@@ -1846,11 +1849,13 @@ def formsemestre_edit_uecoefs(formsemestre_id, err_ue_id=None):
                     )
 
         if not ok:
-            return (
-                "\n".join(H)
+            render_template(
+                "sco_page.j2",
+                title="Coefficients des UE du semestre",
+                content="<h2>Coefficients des UE du semestre</h2>"
+                + "\n".join(H)
                 + "<p><ul><li>%s</li></ul></p>" % "</li><li>".join(msg)
-                + tf[1]
-                + footer
+                + tf[1],
             )
 
         # apply modifications
@@ -1873,20 +1878,25 @@ def formsemestre_edit_uecoefs(formsemestre_id, err_ue_id=None):
                 for ue in ue_deleted:
                     message.append(f"<li>{ue.acronyme}</li>")
                 message.append("</ul>")
+            sco_cache.invalidate_formsemestre(
+                formsemestre_id=formsemestre_id
+            )  # > modif coef UE cap (modifs notes de _certains_ etudiants)
         else:
             message = ["""<h3>Aucune modification</h3>"""]
-        sco_cache.invalidate_formsemestre(
-            formsemestre_id=formsemestre_id
-        )  # > modif coef UE cap (modifs notes de _certains_ etudiants)
 
-        return f"""{html_sco_header.html_sem_header("Coefficients des UE du semestre")}
-            {" ".join(message)}
-            <p><a class="stdlink" href="{url_for("notes.formsemestre_status",
+        return render_template(
+            "sco_page.j2",
+            title="Coefficients des UE du semestre",
+            content=f"""
+                <h2>Coefficients des UE du semestre</h2>
+                {" ".join(message)}
+                <p><a class="stdlink" href="{url_for(
+                    "notes.formsemestre_status",
                     scodoc_dept=g.scodoc_dept, formsemestre_id=formsemestre_id)
                 }">Revenir au tableau de bord</a>
             </p>
-            {footer}
-            """
+            """,
+        )
 
 
 def _get_sem_ues_modimpls(
diff --git a/app/scodoc/sco_formsemestre_inscriptions.py b/app/scodoc/sco_formsemestre_inscriptions.py
index 9c234ff5..bdd73ebb 100644
--- a/app/scodoc/sco_formsemestre_inscriptions.py
+++ b/app/scodoc/sco_formsemestre_inscriptions.py
@@ -31,7 +31,7 @@ import collections
 import time
 
 import flask
-from flask import flash, url_for, g, request
+from flask import flash, url_for, g, render_template, request
 
 from app import db
 from app.comp import res_sem
@@ -428,12 +428,7 @@ def formsemestre_inscription_with_modules(
     etud = Identite.get_etud(etudid)
     if etud.dept_id != formsemestre.dept_id:
         raise ScoValueError("l'étudiant n'est pas dans ce département")
-    H = [
-        html_sco_header.html_sem_header(
-            f"Inscription de {etud.nomprenom} dans ce semestre",
-        )
-    ]
-    footer = html_sco_header.sco_footer()
+    H = []
     # Check 1: déjà inscrit ici ?
     inscr = FormSemestreInscription.query.filter_by(
         etudid=etud.id, formsemestre_id=formsemestre.id
@@ -456,7 +451,12 @@ def formsemestre_inscription_with_modules(
             </ul>
         """
         )
-        return "\n".join(H) + footer
+        return render_template(
+            "sco_page.j2",
+            title=f"Inscription de {etud.nomprenom} dans ce semestre",
+            content=f"<h2>Inscription de {etud.nomprenom} dans ce semestre</h2>"
+            + "\n".join(H),
+        )
     # Check 2: déjà inscrit dans un semestre recouvrant les même dates ?
     # Informe et propose dé-inscriptions
     others = est_inscrit_ailleurs(etudid, formsemestre_id)
@@ -494,7 +494,12 @@ def formsemestre_inscription_with_modules(
             </p>"""
             # was sco_groups.make_query_groups(group_ids)
         )
-        return "\n".join(H) + footer
+        return render_template(
+            "sco_page.j2",
+            title=f"Inscription de {etud.nomprenom} dans ce semestre",
+            content=f"<h2>Inscription de {etud.nomprenom} dans ce semestre</h2>"
+            + "\n".join(H),
+        )
     #
     if group_ids is not None:
         # OK, inscription
@@ -527,7 +532,12 @@ def formsemestre_inscription_with_modules(
         </form>
         """
         )
-        return "\n".join(H) + footer
+        return render_template(
+            "sco_page.j2",
+            title=f"Inscription de {etud.nomprenom} dans ce semestre",
+            content=f"<h2>Inscription de {etud.nomprenom} dans ce semestre</h2>"
+            + "\n".join(H),
+        )
 
 
 def formsemestre_inscription_option(etudid, formsemestre_id):
@@ -855,12 +865,7 @@ def formsemestre_inscrits_ailleurs(formsemestre_id):
     """Page listant les étudiants inscrits dans un autre semestre
     dont les dates recouvrent le semestre indiqué.
     """
-    H = [
-        html_sco_header.html_sem_header(
-            "Inscriptions multiples parmi les étudiants du semestre ",
-            javascripts=["js/etud_info.js"],
-        )
-    ]
+    H = []
     insd = list_inscrits_ailleurs(formsemestre_id)
     # liste ordonnée par nom
     etudlist = [Identite.get_etud(etudid) for etudid, sems in insd.items() if sems]
@@ -912,4 +917,9 @@ def formsemestre_inscrits_ailleurs(formsemestre_id):
         )
     else:
         H.append("""<p>Aucun étudiant en inscription multiple (c'est normal) !</p>""")
-    return "\n".join(H) + html_sco_header.sco_footer()
+    return render_template(
+        "sco_page.j2",
+        title="Inscriptions multiples parmi les étudiants du semestre",
+        content="<h2>Inscriptions multiples parmi les étudiants du semestre</h2>"
+        + "\n".join(H),
+    )
diff --git a/app/scodoc/sco_formsemestre_status.py b/app/scodoc/sco_formsemestre_status.py
index fb0be872..6b5d1a3a 100755
--- a/app/scodoc/sco_formsemestre_status.py
+++ b/app/scodoc/sco_formsemestre_status.py
@@ -741,9 +741,7 @@ def formsemestre_description_table(
         columns_ids=columns_ids,
         html_caption=title,
         html_class="table_leftalign formsemestre_description",
-        html_title=html_sco_header.html_sem_header(
-            "Description du semestre", with_page_header=False
-        ),
+        html_title="<h2>Description du semestre</h2>",
         origin=f"Généré par {sco_version.SCONAME} le {scu.timedate_human_repr()}",
         page_title=title,
         pdf_title=title,
@@ -977,9 +975,6 @@ def formsemestre_status_head(formsemestre_id: int = None, page_title: str = None
     page_title = page_title or "Modules de "
 
     H = [
-        html_sco_header.html_sem_header(
-            page_title, with_page_header=False, with_h2=False
-        ),
         f"""<table>
           <tr><td class="fichetitre2">Formation: </td><td>
          <a href="{url_for('notes.ue_table',
diff --git a/app/scodoc/sco_formsemestre_validation.py b/app/scodoc/sco_formsemestre_validation.py
index e685fbc7..dfac0461 100644
--- a/app/scodoc/sco_formsemestre_validation.py
+++ b/app/scodoc/sco_formsemestre_validation.py
@@ -30,8 +30,7 @@
 import time
 
 import flask
-from flask import url_for, flash, g, request
-from flask.templating import render_template
+from flask import flash, g, render_template, request, url_for
 import sqlalchemy as sa
 
 from app.models import Identite, Evaluation
@@ -957,11 +956,13 @@ def form_decision_manuelle(Se, formsemestre_id, etudid, desturl="", sortcol=None
 
 
 # -----------
-def formsemestre_validation_auto(formsemestre_id):
-    "Formulaire saisie automatisee des decisions d'un semestre"
-    H = [
-        html_sco_header.html_sem_header("Saisie automatique des décisions du semestre"),
-        f"""
+def formsemestre_validation_auto(formsemestre_id: int):
+    "Formulaire saisie automatisée des décisions d'un semestre"
+    return render_template(
+        "sco_page.j2",
+        title="Saisie automatique des décisions",
+        content=f"""
+    <h2>Saisie automatique des décisions du semestre</h2>
     <ul>
     <li>Seuls les étudiants qui obtiennent le semestre seront affectés (code ADM, moyenne générale et
     toutes les barres, semestre précédent validé);</li>
@@ -978,9 +979,7 @@ def formsemestre_validation_auto(formsemestre_id):
     <p><em>Le calcul prend quelques minutes, soyez patients !</em></p>
     </form>
     """,
-        html_sco_header.sco_footer(),
-    ]
-    return "\n".join(H)
+    )
 
 
 def do_formsemestre_validation_auto(formsemestre_id):
diff --git a/app/scodoc/sco_groups_view.py b/app/scodoc/sco_groups_view.py
index 2988dc3e..2114f20c 100644
--- a/app/scodoc/sco_groups_view.py
+++ b/app/scodoc/sco_groups_view.py
@@ -57,14 +57,11 @@ from app.scodoc.sco_exceptions import ScoValueError, ScoPermissionDenied
 from app.scodoc.sco_permissions import Permission
 
 JAVASCRIPTS = html_sco_header.BOOTSTRAP_JS + [
-    "js/etud_info.js",
     "js/groups_view.js",
     "js/multi-select.js",
 ]
 
-CSSSTYLES = html_sco_header.BOOTSTRAP_CSS + [
-    "libjs/bootstrap/css/bootstrap.min.css",
-]
+CSSSTYLES = html_sco_header.BOOTSTRAP_CSS
 
 
 # view:
@@ -615,8 +612,8 @@ def groups_table(
         etud_info["_nom_disp_order"] = etud_sort_key(etud_info)
         etud_info["_prenom_target"] = fiche_url
 
-        etud_info["_nom_disp_td_attrs"] = (
-            'id="%s" class="etudinfo"' % (etud_info["etudid"])
+        etud_info["_nom_disp_td_attrs"] = 'id="%s" class="etudinfo"' % (
+            etud_info["etudid"]
         )
         etud_info["bourse_str"] = "oui" if etud_info["boursier"] else "non"
         if etud_info["etat"] == "D":
diff --git a/app/scodoc/sco_inscr_passage.py b/app/scodoc/sco_inscr_passage.py
index 347105f5..28b65a0a 100644
--- a/app/scodoc/sco_inscr_passage.py
+++ b/app/scodoc/sco_inscr_passage.py
@@ -36,8 +36,6 @@ from flask import url_for, g, request
 import app.scodoc.notesdb as ndb
 import app.scodoc.sco_utils as scu
 from app import db, log
-from app.comp import res_sem
-from app.comp.res_compat import NotesTableCompat
 from app.models import Formation, FormSemestre, GroupDescr, Identite
 from app.scodoc.gen_tables import GenTable
 from app.scodoc import html_sco_header
@@ -315,7 +313,6 @@ def formsemestre_inscr_passage(
     H = [
         html_sco_header.sco_header(
             page_title="Passage des étudiants",
-            javascripts=["js/etud_info.js"],
         )
     ]
     footer = html_sco_header.sco_footer()
@@ -487,10 +484,9 @@ def _build_page(
     else:
         ignore_jury_checked = ""
     H = [
-        html_sco_header.html_sem_header(
-            "Passages dans le semestre", with_page_header=False
-        ),
-        f"""<form name="f" method="post" action="{request.base_url}">
+        f"""
+        <h2 class="formsemestre">Passages dans le semestre</h2>
+        <form name="f" method="post" action="{request.base_url}">
 
         <input type="hidden" name="formsemestre_id" value="{formsemestre.id}"/>
 
@@ -589,7 +585,7 @@ def formsemestre_inscr_passage_help(formsemestre: FormSemestre):
 
 def etuds_select_boxes(
     auth_etuds_by_cat,
-    inscrits_ailleurs={},
+    inscrits_ailleurs: dict = None,
     sel_inscrits=True,
     show_empty_boxes=False,
     export_cat_xls=None,
@@ -602,6 +598,7 @@ def etuds_select_boxes(
     sel_inscrits=
     export_cat_xls =
     """
+    inscrits_ailleurs = inscrits_ailleurs or {}
     if export_cat_xls:
         return etuds_select_box_xls(auth_etuds_by_cat[export_cat_xls])
 
@@ -633,7 +630,7 @@ def etuds_select_boxes(
     for src_cat in auth_etuds_by_cat.keys():
         infos = auth_etuds_by_cat[src_cat]["infos"]
         infos["comment"] = infos.get("comment", "")  # commentaire dans sous-titre boite
-        help = infos.get("help", "")
+        help_txt = infos.get("help", "")
         etuds = auth_etuds_by_cat[src_cat]["etuds"]
         etuds.sort(key=itemgetter("nom"))
         with_checkbox = (not read_only) and auth_etuds_by_cat[src_cat]["infos"].get(
@@ -650,8 +647,8 @@ def etuds_select_boxes(
                 <div class="pas_sembox_title"><a href="%(title_target)s" """
                 % infos
             )
-            if help:  # bubble
-                H.append('title="%s"' % help)
+            if help_txt:  # bubble
+                H.append('title="%s"' % help_txt)
             H.append(
                 """>%(title)s</a></div>
                 <div class="pas_sembox_subtitle">(%(nbetuds)d étudiants%(comment)s)"""
diff --git a/app/scodoc/sco_lycee.py b/app/scodoc/sco_lycee.py
index 79273e16..4b2f262e 100644
--- a/app/scodoc/sco_lycee.py
+++ b/app/scodoc/sco_lycee.py
@@ -98,7 +98,7 @@ def scodoc_table_etuds_lycees(fmt="html"):
         html_sco_header.sco_header(
             page_title=tab.page_title,
             init_google_maps=True,
-            javascripts=["js/etud_info.js", "js/map_lycees.js"],
+            javascripts=["js/map_lycees.js"],
         ),
         """<h2 class="formsemestre">Lycées d'origine des %d étudiants (%d semestres)</h2>"""
         % (len(etuds), len(semdepts)),
@@ -219,8 +219,7 @@ def formsemestre_etuds_lycees(
             page_title=tab.page_title,
             init_google_maps=True,
             cssstyles=sco_groups_view.CSSSTYLES,
-            javascripts=sco_groups_view.JAVASCRIPTS
-            + ["js/etud_info.js", "js/map_lycees.js"],
+            javascripts=sco_groups_view.JAVASCRIPTS + ["js/map_lycees.js"],
         ),
         """<h2 class="formsemestre">Lycées d'origine des étudiants</h2>""",
         "\n".join(F),
diff --git a/app/scodoc/sco_moduleimpl_inscriptions.py b/app/scodoc/sco_moduleimpl_inscriptions.py
index cbd77ff9..4e2e8674 100644
--- a/app/scodoc/sco_moduleimpl_inscriptions.py
+++ b/app/scodoc/sco_moduleimpl_inscriptions.py
@@ -31,7 +31,7 @@ import collections
 from operator import attrgetter
 
 import flask
-from flask import url_for, g, request
+from flask import url_for, g, render_template, request
 from flask_login import current_user
 
 from app import db, log
@@ -84,7 +84,6 @@ def moduleimpl_inscriptions_edit(
         return  # can_change_inscriptions raises exception
     header = html_sco_header.sco_header(
         page_title="Inscription au module",
-        javascripts=["js/etud_info.js"],
     )
     footer = html_sco_header.sco_footer()
     H = [
@@ -301,14 +300,12 @@ def moduleimpl_inscriptions_stats(formsemestre_id):
 
     # Page HTML:
     H = [
-        html_sco_header.html_sem_header(
-            "Inscriptions aux modules et UE du semestre",
-            javascripts=["js/etud_info.js", "js/moduleimpl_inscriptions_stats.js"],
-        )
+        f"""
+         <h2 class="formsemestre">Inscriptions aux modules et UE du semestre</h2>
+         <h3>Inscrits au semestre: {len(inscrits)} étudiants</h3>
+        """
     ]
 
-    H.append(f"<h3>Inscrits au semestre: {len(inscrits)} étudiants</h3>")
-
     if options:
         H.append("<h3>Modules auxquels tous les étudiants ne sont pas inscrits:</h3>")
         H.append(
@@ -495,8 +492,12 @@ def moduleimpl_inscriptions_stats(formsemestre_id):
     """
     )
 
-    H.append(html_sco_header.sco_footer())
-    return "\n".join(H)
+    return render_template(
+        "sco_page.j2",
+        title="Inscriptions aux modules et UE du semestre",
+        javascripts=["js/moduleimpl_inscriptions_stats.js"],
+        content="\n".join(H),
+    )
 
 
 def _list_but_ue_inscriptions(res: NotesTableCompat, read_only: bool = True) -> str:
diff --git a/app/scodoc/sco_poursuite_dut.py b/app/scodoc/sco_poursuite_dut.py
index 628608a5..42fcd310 100644
--- a/app/scodoc/sco_poursuite_dut.py
+++ b/app/scodoc/sco_poursuite_dut.py
@@ -233,7 +233,6 @@ def formsemestre_poursuite_report(formsemestre_id, fmt="html"):
     tab.base_url = "%s?formsemestre_id=%s" % (request.base_url, formsemestre_id)
     return tab.make_page(
         title="""<h2 class="formsemestre">Poursuite d'études</h2>""",
-        javascripts=["js/etud_info.js"],
         fmt=fmt,
         with_html_headers=True,
     )
diff --git a/app/scodoc/sco_preferences.py b/app/scodoc/sco_preferences.py
index 6b940076..b7083eea 100644
--- a/app/scodoc/sco_preferences.py
+++ b/app/scodoc/sco_preferences.py
@@ -112,7 +112,7 @@ get_base_preferences(formsemestre_id)
 """
 
 import flask
-from flask import current_app, flash, g, request, url_for
+from flask import current_app, flash, g, render_template, request, url_for
 
 from app import db, log
 from app.models import Departement
@@ -2243,14 +2243,8 @@ class BasePreferences:
 
     def edit(self):
         """HTML dialog: edit global preferences"""
-        from app.scodoc import html_sco_header
-
         self.load()
         H = [
-            html_sco_header.sco_header(
-                page_title=f"Préférences {g.scodoc_dept}",
-                javascripts=["js/detail_summary_persistence.js"],
-            ),
             f"<h2>Préférences globales pour le département {g.scodoc_dept}</h2>",
             # f"""<p><a href="{url_for("scodoc.configure_logos", scodoc_dept=g.scodoc_dept)
             # }">modification des logos du département (pour documents pdf)</a></p>"""
@@ -2277,7 +2271,12 @@ class BasePreferences:
         )
         dest_url = url_for("scolar.index_html", scodoc_dept=g.scodoc_dept)
         if tf[0] == 0:
-            return "\n".join(H) + tf[1] + html_sco_header.sco_footer()
+            return render_template(
+                "sco_page_dept.j2",
+                content="\n".join(H) + tf[1],
+                title=f"Préférences {g.scodoc_dept}",
+                javascripts=["js/detail_summary_persistence.js"],
+            )
         if tf[0] == -1:
             return flask.redirect(dest_url)  # cancel
         #
@@ -2384,18 +2383,11 @@ class SemPreferences:
     # The dialog
     def edit(self, categories=[]):
         """Dialog to edit semestre preferences in given categories"""
-        from app.scodoc import html_sco_header
-        from app.scodoc import sco_formsemestre
-
         if not self.formsemestre_id:
             raise ScoValueError(
                 "sem_preferences.edit doit etre appele sur un semestre !"
             )  # a bug !
         H = [
-            html_sco_header.html_sem_header(
-                "Préférences du semestre",
-                javascripts=["js/detail_summary_persistence.js"],
-            ),
             """
 <p class="help">Les paramètres définis ici ne s'appliqueront qu'à ce semestre.</p>
 <p class="msg">Attention: cliquez sur "Enregistrer les modifications" en bas de page pour appliquer vos changements !</p>
@@ -2456,7 +2448,12 @@ function set_global_pref(el, pref_name) {
         )
 
         if tf[0] == 0:
-            return "\n".join(H) + tf[1] + html_sco_header.sco_footer()
+            return render_template(
+                "sco_page.j2",
+                content="\n".join(H) + tf[1],
+                title="Préférences du semestre",
+                javascripts=["js/detail_summary_persistence.js"],
+            )
         elif tf[0] == -1:
             flash("Annulé")
             return flask.redirect(dest_url)
diff --git a/app/scodoc/sco_pv_forms.py b/app/scodoc/sco_pv_forms.py
index d90363aa..fa651942 100644
--- a/app/scodoc/sco_pv_forms.py
+++ b/app/scodoc/sco_pv_forms.py
@@ -35,8 +35,7 @@ from reportlab.platypus import Paragraph
 from reportlab.lib import styles
 
 import flask
-from flask import flash, redirect, url_for
-from flask import g, request
+from flask import flash, g, redirect, render_template, request, url_for
 
 from app.models import FormSemestre, Identite
 
@@ -223,15 +222,13 @@ def formsemestre_pvjury(formsemestre_id, fmt="html", publish=True):
             )
         )
 
-    footer = html_sco_header.sco_footer()
-
     dpv = sco_pv_dict.dict_pvjury(formsemestre_id, with_prev=True)
     if not dpv:
         if fmt == "html":
-            return (
-                html_sco_header.sco_header()
-                + "<h2>Aucune information disponible !</h2>"
-                + footer
+            return render_template(
+                "sco_page.j2",
+                title="PV Jury",
+                content="<h2>Aucune information disponible !</h2>",
             )
         else:
             return None
@@ -262,11 +259,10 @@ def formsemestre_pvjury(formsemestre_id, fmt="html", publish=True):
         )
     tab.base_url = "%s?formsemestre_id=%s" % (request.base_url, formsemestre_id)
     H = [
-        html_sco_header.html_sem_header(
-            "Décisions du jury pour le semestre",
-            javascripts=["js/etud_info.js"],
-        ),
-        """<p>(dernière modif le %s)</p>""" % dpv["date"],
+        f"""
+        <h2 class="formsemestre">Décisions du jury pour le semestre</h2>
+        <p>(dernière modif le {dpv["date"]})</p>
+        """,
     ]
 
     H.append(
@@ -333,7 +329,9 @@ def formsemestre_pvjury(formsemestre_id, fmt="html", publish=True):
     """
     )
     H.append("</div>")  # /codes
-    return "\n".join(H) + footer
+    return render_template(
+        "sco_page.j2", title="Décisions du jury pour le semestre", content="\n".join(H)
+    )
 
 
 # ---------------------------------------------------------------------------
@@ -351,9 +349,6 @@ def formsemestre_pvjury_pdf(formsemestre_id, group_ids: list[int] = None, etudid
     if etudid:
         # PV pour ce seul étudiant:
         etud = Identite.get_etud(etudid)
-        etuddescr = f"""<a class="discretelink" href="{
-            url_for("scolar.fiche_etud", scodoc_dept=g.scodoc_dept, etudid=etudid)
-        }">{etud.nomprenom}</a>"""
         etudids = [etudid]
     else:
         etuddescr = ""
@@ -367,11 +362,6 @@ def formsemestre_pvjury_pdf(formsemestre_id, group_ids: list[int] = None, etudid
         etudids = [m["etudid"] for m in groups_infos.members]
 
     H = [
-        html_sco_header.html_sem_header(
-            f"Édition du PV de jury {etuddescr}",
-            javascripts=sco_groups_view.JAVASCRIPTS,
-            cssstyles=sco_groups_view.CSSSTYLES,
-        ),
         f"""<div class="help">Utiliser cette page pour éditer des versions provisoires des PV.
           <span class="fontred">Il est recommandé d'archiver les versions définitives:
            <a class="stdlink" href="{url_for(
@@ -384,7 +374,6 @@ def formsemestre_pvjury_pdf(formsemestre_id, group_ids: list[int] = None, etudid
         """<p><em>Voir aussi si besoin les réglages sur la page "Paramétrage"
         (accessible à l'administrateur du département).</em>
         </p>""",
-        html_sco_header.sco_footer(),
     ]
     descr = descrform_pvjury(formsemestre)
     if etudid:
@@ -409,7 +398,20 @@ def formsemestre_pvjury_pdf(formsemestre_id, group_ids: list[int] = None, etudid
         html_foot_markup=menu_choix_groupe,
     )
     if tf[0] == 0:
-        return "\n".join(H) + "\n" + tf[1] + "\n".join(F)
+        return render_template(
+            "sco_page.j2",
+            title=f"Édition du PV de jury de {etud.nom_prenom()}",
+            content=f"""<h2 class="formsemestre">Édition du PV de jury
+            de <a class="discretelink" href="{
+            url_for("scolar.fiche_etud", scodoc_dept=g.scodoc_dept, etudid=etudid)
+            }">{etud.nomprenom}</a></h2>"""
+            + "\n".join(H)
+            + "\n"
+            + tf[1]
+            + "\n".join(F),
+            javascripts=sco_groups_view.JAVASCRIPTS,
+            cssstyles=sco_groups_view.CSSSTYLES,
+        )
     elif tf[0] == -1:
         return flask.redirect(
             url_for(
@@ -541,7 +543,7 @@ def descrform_pvjury(formsemestre: FormSemestre):
     ]
 
 
-def formsemestre_lettres_individuelles(formsemestre_id, group_ids=[]):
+def formsemestre_lettres_individuelles(formsemestre_id, group_ids=()):
     "Lettres avis jury en PDF"
     formsemestre: FormSemestre = FormSemestre.query.get_or_404(formsemestre_id)
     if not group_ids:
@@ -553,12 +555,9 @@ def formsemestre_lettres_individuelles(formsemestre_id, group_ids=[]):
     etudids = [m["etudid"] for m in groups_infos.members]
 
     H = [
-        html_sco_header.html_sem_header(
-            "Édition des lettres individuelles",
-            javascripts=sco_groups_view.JAVASCRIPTS,
-            cssstyles=sco_groups_view.CSSSTYLES,
-        ),
-        f"""<p class="help">Utiliser cette page pour éditer des versions provisoires des PV.
+        f"""
+        <h2 class="formsemestre">Édition des lettres individuelles</h2>
+        <p class="help">Utiliser cette page pour éditer des versions provisoires des PV.
           <span class="fontred">Il est recommandé d'archiver les versions définitives: <a
           href="{url_for(
                 "notes.formsemestre_archive",
@@ -568,7 +567,6 @@ def formsemestre_lettres_individuelles(formsemestre_id, group_ids=[]):
           >voir cette page</a></span></p>
          """,
     ]
-    F = html_sco_header.sco_footer()
     descr = descrform_lettres_individuelles()
     menu_choix_groupe = (
         """<div class="group_ids_sel_menu">Groupes d'étudiants à lister: """
@@ -587,7 +585,13 @@ def formsemestre_lettres_individuelles(formsemestre_id, group_ids=[]):
         html_foot_markup=menu_choix_groupe,
     )
     if tf[0] == 0:
-        return "\n".join(H) + "\n" + tf[1] + F
+        return render_template(
+            "sco_page.j2",
+            title="Édition des lettres individuelles",
+            content="\n".join(H) + "\n" + tf[1],
+            javascripts=sco_groups_view.JAVASCRIPTS,
+            cssstyles=sco_groups_view.CSSSTYLES,
+        )
     elif tf[0] == -1:
         return flask.redirect(
             url_for(
diff --git a/app/scodoc/sco_recapcomplet.py b/app/scodoc/sco_recapcomplet.py
index 35eb2f81..33b8bb17 100644
--- a/app/scodoc/sco_recapcomplet.py
+++ b/app/scodoc/sco_recapcomplet.py
@@ -123,7 +123,7 @@ def formsemestre_recapcomplet(
             page_title=f"{formsemestre.sem_modalite()}: "
             + ("jury" if mode_jury else "moyennes"),
             no_sidebar=True,
-            javascripts=["js/etud_info.js", "js/table_recap.js"],
+            javascripts=["js/table_recap.js"],
         ),
         sco_formsemestre_status.formsemestre_status_head(
             formsemestre_id=formsemestre_id
diff --git a/app/scodoc/sco_report.py b/app/scodoc/sco_report.py
index 5fa740c6..2f316bd6 100644
--- a/app/scodoc/sco_report.py
+++ b/app/scodoc/sco_report.py
@@ -1367,7 +1367,6 @@ def formsemestre_suivi_cursus(
     H = [
         html_sco_header.sco_header(
             page_title=tab.page_title,
-            javascripts=["js/etud_info.js"],
         ),
         """<h2 class="formsemestre">Cursus suivis par les étudiants de ce semestre</h2>""",
         "\n".join(F),
diff --git a/app/scodoc/sco_synchro_etuds.py b/app/scodoc/sco_synchro_etuds.py
index f92c9a38..869444ef 100644
--- a/app/scodoc/sco_synchro_etuds.py
+++ b/app/scodoc/sco_synchro_etuds.py
@@ -168,12 +168,7 @@ def formsemestre_synchro_etuds(
             suffix=scu.XLSX_SUFFIX,
         )
 
-    H = [
-        html_sco_header.sco_header(
-            page_title="Synchronisation étudiants",
-            javascripts=["js/etud_info.js"],
-        )
-    ]
+    H = [html_sco_header.sco_header(page_title="Synchronisation étudiants")]
     if not submitted:
         H += _build_page(
             sem,
diff --git a/app/scodoc/sco_ue_external.py b/app/scodoc/sco_ue_external.py
index 2ad5b53a..6d529b28 100644
--- a/app/scodoc/sco_ue_external.py
+++ b/app/scodoc/sco_ue_external.py
@@ -54,14 +54,13 @@ Solution proposée (nov 2014):
 
 """
 import flask
-from flask import flash, g, request, url_for
+from flask import flash, g, request, render_template, url_for
 from flask_login import current_user
 from app.models.formsemestre import FormSemestre
 
 
 from app import db, log
 from app.models import Evaluation, Identite, ModuleImpl, UniteEns
-from app.scodoc import html_sco_header
 from app.scodoc import codes_cursus
 from app.scodoc import sco_edit_matiere
 from app.scodoc import sco_edit_module
@@ -240,11 +239,9 @@ def external_ue_create_form(formsemestre_id: int, etudid: int):
     existing_external_ue = get_existing_external_ue(formation_id)
 
     H = [
-        html_sco_header.html_sem_header(
-            f"Ajout d'une UE externe pour {etud.nomprenom}",
-            javascripts=["js/sco_ue_external.js"],
-        ),
-        """<p class="help">Cette page permet d'indiquer que l'étudiant a suivi une UE
+        f"""
+    <h2 class="formsemestre">Ajout d'une UE externe pour {etud.nomprenom}</h2>
+    <p class="help">Cette page permet d'indiquer que l'étudiant a suivi une UE
     dans un autre établissement et qu'elle doit être intégrée dans le semestre courant.<br>
     La note (/20) obtenue par l'étudiant doit toujours être spécifiée.</br>
     On peut choisir une UE externe existante (dans le menu), ou bien en créer une, qui sera
@@ -252,7 +249,6 @@ def external_ue_create_form(formsemestre_id: int, etudid: int):
     </p>
     """,
     ]
-    html_footer = html_sco_header.sco_footer()
     parcours = formsemestre.formation.get_cursus()
     ue_types = [
         typ for typ in parcours.ALLOWED_UE_TYPES if typ != codes_cursus.UE_SPORT
@@ -349,7 +345,12 @@ def external_ue_create_form(formsemestre_id: int, etudid: int):
         etudid=etudid,
     )
     if tf[0] == 0:
-        return "\n".join(H) + "\n" + tf[1] + html_footer
+        return render_template(
+            "sco_page.j2",
+            title=f"Ajout d'une UE externe pour {etud.nomprenom}",
+            javascripts=["js/sco_ue_external.js"],
+            content="\n".join(H) + "\n" + tf[1],
+        )
     elif tf[0] == -1:
         return flask.redirect(bull_url)
     else:
@@ -358,12 +359,14 @@ def external_ue_create_form(formsemestre_id: int, etudid: int):
             note, 20.0, etudid=etudid, absents=[], invalids=[]
         )
         if invalid:
-            return (
-                "\n".join(H)
+            return render_template(
+                "sco_page.j2",
+                title=f"Ajout d'une UE externe pour {etud.nomprenom}",
+                javascripts=["js/sco_ue_external.js"],
+                content="\n".join(H)
                 + "\n"
                 + tf_error_message("valeur note invalide")
-                + tf[1]
-                + html_footer
+                + tf[1],
             )
         if tf[2]["existing_ue"]:
             ue_id = int(tf[2]["existing_ue"])
@@ -371,12 +374,14 @@ def external_ue_create_form(formsemestre_id: int, etudid: int):
         else:
             acronyme = tf[2]["acronyme"].strip()
             if not acronyme:
-                return (
-                    "\n".join(H)
+                return render_template(
+                    "sco_page.j2",
+                    title=f"Ajout d'une UE externe pour {etud.nomprenom}",
+                    javascripts=["js/sco_ue_external.js"],
+                    content="\n".join(H)
                     + "\n"
                     + tf_error_message("spécifier acronyme d'UE")
-                    + tf[1]
-                    + html_footer
+                    + tf[1],
                 )
             modimpl = external_ue_create(
                 formsemestre_id,
diff --git a/app/scodoc/sco_users.py b/app/scodoc/sco_users.py
index f1ab3191..90d1688e 100644
--- a/app/scodoc/sco_users.py
+++ b/app/scodoc/sco_users.py
@@ -31,7 +31,7 @@
 # Anciennement ZScoUsers.py, fonctions de gestion des données réécrites avec flask/SQLAlchemy
 import re
 
-from flask import url_for, g, request
+from flask import url_for, g, render_template, request
 from flask_login import current_user
 
 
@@ -54,7 +54,7 @@ def index_html(
     all_depts = int(all_depts)
     with_inactives = int(with_inactives)
 
-    H = [html_sco_header.html_sem_header("Gestion des utilisateurs")]
+    H = ["<h2>Gestion des utilisateurs</h2>"]
 
     if current_user.has_permission(Permission.UsersAdmin, g.scodoc_dept):
         H.append(
@@ -112,6 +112,7 @@ def index_html(
             raise ScoValueError("nom de rôle invalide")
     else:
         having_role = None
+
     content = list_users(
         g.scodoc_dept,
         all_depts=all_depts,
@@ -122,10 +123,12 @@ def index_html(
     )
     if fmt != "html":
         return content
+
     H.append(content)
 
-    F = html_sco_header.sco_footer()
-    return "\n".join(H) + F
+    return render_template(
+        "sco_page.j2", content="\n".join(H), title="Gestion des utilisateurs"
+    )
 
 
 def list_users(
diff --git a/app/static/css/scodoc.css b/app/static/css/scodoc.css
index 24e4d1e6..e147f51e 100644
--- a/app/static/css/scodoc.css
+++ b/app/static/css/scodoc.css
@@ -686,11 +686,6 @@ div.scobox.news {
   background-color: rgb(255, 235, 170);
 }
 
-div.news a,
-div.news a.stdlink {
-  color: black;
-  text-decoration: none;
-}
 
 div.news a:hover {
   color: rgb(153, 51, 51);
diff --git a/app/templates/sco_page.j2 b/app/templates/sco_page.j2
index 9c57af97..79204200 100644
--- a/app/templates/sco_page.j2
+++ b/app/templates/sco_page.j2
@@ -43,14 +43,18 @@
                 <img id="toggle-sidebar-img" src="{{scu.STATIC_DIR}}/icons/back.svg" width="12px" alt="toggle sidebar"/>
             </div>
         </div>
-        <div class="formsemestre-page-header">
+        {% if sco.formsemestre %}
+            <div class="formsemestre-page-header">
+                {% include "flashed_messages.j2" %}
+                {% if sco.formsemestre %}
+                    {% block formsemestre_header %}
+                        {% include "formsemestre_header.j2" %}
+                    {% endblock %}
+                {% endif %}
+            </div>
+        {% else %}
             {% include "flashed_messages.j2" %}
-            {% if sco.formsemestre %}
-                {% block formsemestre_header %}
-                    {% include "formsemestre_header.j2" %}
-                {% endblock %}
-            {% endif %}
-        </div>
+        {% endif %}
         <div id="sidebar">
             {% include "sidebar.j2" %}
         </div>
@@ -73,6 +77,7 @@
 <script src="{{scu.STATIC_DIR}}/libjs/menu.js"></script>
 <script src="{{scu.STATIC_DIR}}/libjs/bubble.js"></script>
 <script src="{{scu.STATIC_DIR}}/js/scodoc.js"></script>
+<script src="{{scu.STATIC_DIR}}/js/etud_info.js"></script>
 <script src="{{scu.STATIC_DIR}}/DataTables/datatables.min.js"></script>
 <script>
     window.onload = function () {
diff --git a/app/views/absences.py b/app/views/absences.py
index ad10908e..d3f26ebb 100644
--- a/app/views/absences.py
+++ b/app/views/absences.py
@@ -246,7 +246,6 @@ def list_billets():
     H = [
         html_sco_header.sco_header(
             page_title="Billet d'absence non traités",
-            javascripts=["js/etud_info.js"],
         ),
         f"<h2>Billets d'absence en attente de traitement ({table.get_nb_rows()})</h2>",
     ]
diff --git a/app/views/notes.py b/app/views/notes.py
index 587132cd..3d8746a1 100644
--- a/app/views/notes.py
+++ b/app/views/notes.py
@@ -942,18 +942,12 @@ def edit_enseignants_form(moduleimpl_id):
     "modif liste enseignants/moduleimpl"
     modimpl = ModuleImpl.get_modimpl(moduleimpl_id)
     modimpl.can_change_ens(raise_exc=True)
-    # --
-    header = html_sco_header.html_sem_header(
-        f"""Enseignants du <a href="{
+    #
+    page_title = f"Enseignants du module {modimpl.module.titre or modimpl.module.code}"
+    title = f"""Enseignants du <a href="{
             url_for("notes.moduleimpl_status",
             scodoc_dept=g.scodoc_dept, moduleimpl_id=modimpl.id)
-        }">module {modimpl.module.titre or modimpl.module.code}</a>""",
-        page_title=f"Enseignants du module {modimpl.module.titre or modimpl.module.code}",
-        javascripts=["libjs/AutoSuggest.js"],
-        cssstyles=["css/autosuggest_inquisitor.css"],
-    )
-    footer = html_sco_header.sco_footer()
-
+        }">module {modimpl.module.titre or modimpl.module.code}</a>"""
     # Liste des enseignants avec forme pour affichage / saisie avec suggestion
     userlist = sco_users.get_user_list()
     uid2display = {}  # uid : forme pour affichage = "NOM Prenom (login)"(login)"
@@ -1021,7 +1015,13 @@ def edit_enseignants_form(moduleimpl_id):
         cancelbutton="Annuler",
     )
     if tf[0] == 0:
-        return header + "\n".join(H) + tf[1] + F + footer
+        return render_template(
+            "sco_page.j2",
+            title=page_title,
+            content=title + "\n".join(H) + tf[1],
+            javascripts=["libjs/AutoSuggest.js"],
+            cssstyles=["css/autosuggest_inquisitor.css"],
+        )
     elif tf[0] == -1:
         return flask.redirect(
             url_for(
@@ -1058,7 +1058,13 @@ def edit_enseignants_form(moduleimpl_id):
                         moduleimpl_id=moduleimpl_id,
                     )
                 )
-        return header + "\n".join(H) + tf[1] + F + footer
+        return render_template(
+            "sco_page.j2",
+            title=page_title,
+            content=title + "\n".join(H) + tf[1],
+            javascripts=["libjs/AutoSuggest.js"],
+            cssstyles=["css/autosuggest_inquisitor.css"],
+        )
 
 
 @bp.route("/edit_moduleimpl_resp", methods=["GET", "POST"])
@@ -1072,15 +1078,13 @@ def edit_moduleimpl_resp(moduleimpl_id: int):
     modimpl: ModuleImpl = ModuleImpl.query.get_or_404(moduleimpl_id)
     modimpl.can_change_responsable(current_user, raise_exc=True)  # access control
     H = [
-        html_sco_header.html_sem_header(
-            f"""Modification du responsable du <a class="stdlink" href="{
+        f"""<h2 class="formsemestre">Modification du responsable du
+         <a class="stdlink" href="{
                 url_for("notes.moduleimpl_status",
                     scodoc_dept=g.scodoc_dept, moduleimpl_id=moduleimpl_id)
-            }">module {modimpl.module.titre or ""}</a>""",
-            javascripts=["libjs/AutoSuggest.js"],
-            cssstyles=["css/autosuggest_inquisitor.css"],
-        )
+            }">module {modimpl.module.titre or ""}</a></h2>"""
     ]
+
     help_str = """<p class="help">Taper le début du nom de l'enseignant.</p>"""
     # Liste des enseignants avec forme pour affichage / saisie avec suggestion
     userlist = [sco_users.user_info(user=u) for u in sco_users.get_user_list()]
@@ -1125,7 +1129,13 @@ def edit_moduleimpl_resp(moduleimpl_id: int):
         initvalues=initvalues,
     )
     if tf[0] == 0:
-        return "\n".join(H) + tf[1] + help_str + html_sco_header.sco_footer()
+        return render_template(
+            "sco_page.j2",
+            content="\n".join(H) + tf[1] + help_str,
+            title="Modification responsable module",
+            javascripts=["libjs/AutoSuggest.js"],
+            cssstyles=["css/autosuggest_inquisitor.css"],
+        )
     elif tf[0] == -1:
         return flask.redirect(
             url_for(
@@ -1198,21 +1208,13 @@ def view_module_abs(moduleimpl_id, fmt="html"):
             }
         )
 
-    H = [
-        html_sco_header.html_sem_header(
-            f"""Absences du <a href="{
-                url_for("notes.moduleimpl_status",
-                    scodoc_dept=g.scodoc_dept, moduleimpl_id=moduleimpl_id
-                )}">module {modimpl.module.titre_str()}</a>""",
-            page_title=f"Absences du module {modimpl.module.titre_str()}",
-        )
-    ]
+    content = f"""
+        <h2>Absences du <a href="{
+            url_for("notes.moduleimpl_status",
+                scodoc_dept=g.scodoc_dept, moduleimpl_id=moduleimpl_id
+            )}">module {modimpl.module.titre_str()}</a></h2>"""
     if not rows and fmt == "html":
-        return (
-            "\n".join(H)
-            + "<p>Aucune absence signalée</p>"
-            + html_sco_header.sco_footer()
-        )
+        content += "<p>Aucune absence signalée</p>"
 
     tab = GenTable(
         titles={
@@ -1236,7 +1238,14 @@ def view_module_abs(moduleimpl_id, fmt="html"):
     if fmt != "html":
         return tab.make_page(fmt=fmt)
 
-    return "\n".join(H) + tab.html() + html_sco_header.sco_footer()
+    if not tab.is_empty():
+        content += tab.html()
+
+    return render_template(
+        "sco_page.j2",
+        content=content,
+        title=f"Absences du module {modimpl.module.titre_str()}",
+    )
 
 
 @bp.route("/delete_ue_expr/<int:formsemestre_id>/<int:ue_id>", methods=["GET", "POST"])
@@ -1337,9 +1346,7 @@ def formsemestre_enseignants_list(formsemestre_id, fmt="html"):
         html_class="table_leftalign formsemestre_enseignants_list",
         html_with_td_classes=True,
         filename=scu.make_filename(f"Enseignants-{formsemestre.titre_annee()}"),
-        html_title=html_sco_header.html_sem_header(
-            "Enseignants du semestre", with_page_header=False
-        ),
+        html_title="""<h2 class="formsemestre">Enseignants du semestre</h2>""",
         base_url=f"{request.base_url}?formsemestre_id={formsemestre_id}",
         caption="""Tous les enseignants (responsables ou associés aux modules de
         ce semestre) apparaissent. Le nombre de saisies d'absences est indicatif.""",
@@ -1646,7 +1653,6 @@ def evaluation_delete(evaluation_id):
     etat = sco_evaluations.do_evaluation_etat(evaluation.id)
     H = [
         f"""
-        {html_sco_header.html_sem_header(tit, with_h2=False)}
         <h2 class="formsemestre">Module <tt>{evaluation.moduleimpl.module.code}</tt>
             {evaluation.moduleimpl.module.titre_str()}</h2>
             <h3>{tit}</h3>
@@ -1682,7 +1688,7 @@ def evaluation_delete(evaluation_id):
             </p>
             </div>"""
         )
-        return "\n".join(H) + html_sco_header.sco_footer()
+        return render_template("sco_page.j2", title=tit, content="\n".join(H))
     if warning:
         H.append("""</div>""")
 
@@ -1695,7 +1701,7 @@ def evaluation_delete(evaluation_id):
         cancelbutton="Annuler",
     )
     if tf[0] == 0:
-        return "\n".join(H) + tf[1] + html_sco_header.sco_footer()
+        return render_template("sco_page.j2", title=tit, content="\n".join(H) + tf[1])
     elif tf[0] == -1:
         return flask.redirect(
             url_for(
@@ -1706,14 +1712,15 @@ def evaluation_delete(evaluation_id):
         )
     else:
         evaluation.delete()
-        return (
-            "\n".join(H)
+        return render_template(
+            "sco_page.j2",
+            title=tit,
+            content="\n".join(H)
             + f"""<p>OK, évaluation supprimée.</p>
         <p><a class="stdlink" href="{
             url_for("notes.moduleimpl_status", scodoc_dept=g.scodoc_dept,
             moduleimpl_id=evaluation.moduleimpl_id)
-            }">Continuer</a></p>"""
-            + html_sco_header.sco_footer()
+            }">Continuer</a></p>""",
         )
 
 
@@ -1771,7 +1778,6 @@ def evaluation_listenotes():
             content=content,
             title=page_title,
             cssstyles=["css/verticalhisto.css"],
-            javascripts=["js/etud_info.js"],
         )
     return content
 
diff --git a/app/views/scolar.py b/app/views/scolar.py
index 6cf569af..60e89d4d 100644
--- a/app/views/scolar.py
+++ b/app/views/scolar.py
@@ -1961,14 +1961,15 @@ def check_group_apogee(group_id, etat=None, fix=False, fixmail=False):
     etat = etat or None
     members, group, _, sem, _ = sco_groups.get_group_infos(group_id, etat=etat)
     formsemestre_id = group["formsemestre_id"]
-
+    title = f"""Étudiants du {group["group_name"] or "semestre"}"""
     cnx = ndb.GetDBConnexion()
     H = [
-        html_sco_header.html_sem_header(
-            "Étudiants du %s" % (group["group_name"] or "semestre")
-        ),
-        '<table class="sortable" id="listegroupe">',
-        "<tr><th>Nom</th><th>Nom usuel</th><th>Prénom</th><th>Mail</th><th>NIP (ScoDoc)</th><th>Apogée</th></tr>",
+        f"""<h2 class="formsemestre">{title}</h2>
+        <table class="sortable" id="listegroupe">
+        <tr>
+        <th>Nom</th><th>Nom usuel</th><th>Prénom</th><th>Mail</th>
+        <th>NIP (ScoDoc)</th><th>Apogée</th>
+        </tr>"""
     ]
     nerrs = 0  # nombre d'anomalies détectées
     nfix = 0  # nb codes changes
@@ -2090,8 +2091,7 @@ def check_group_apogee(group_id, etat=None, fix=False, fixmail=False):
             formsemestre_id,
         )
     )
-
-    return "\n".join(H) + html_sco_header.sco_footer()
+    return render_template("sco_page.j2", title=title, content="\n".join(H))
 
 
 @bp.route("/export_etudiants_courants")
@@ -2483,8 +2483,11 @@ def formsemestre_import_etud_admission(
             diag += "</ul>"
         diag_by_sem[formsemestre.id] = diag
 
-    return f"""
-        { html_sco_header.html_sem_header("Ré-import données admission") }
+    return render_template(
+        "sco_page.j2",
+        title="Ré-import données admission",
+        content=f"""
+        <h2>Ré-import données admission</h2>
         <h3>Opération effectuée</h3>
         <p>Sur le(s) semestres(s):</p>
         <ul>
@@ -2492,8 +2495,8 @@ def formsemestre_import_etud_admission(
             { '</li><li>'.join( [(s.html_link_status() + diag_by_sem[s.id]) for s in formsemestres ]) }
             </li>
         </ul>
-        { html_sco_header.sco_footer() }
-        """
+        """,
+    )
 
 
 sco_publish(
-- 
GitLab