diff --git a/app/models/assiduites.py b/app/models/assiduites.py
index 44cf1fc1e7e83dc0e354ddb1fb24c28d1cf5a0d0..c920bbe4490a17a8329165b008f5da00550d533f 100644
--- a/app/models/assiduites.py
+++ b/app/models/assiduites.py
@@ -141,7 +141,7 @@ class Assiduite(ScoDocModel):
             }"""
 
     def __repr__(self) -> str:
-        return f"<Assiduite {self.id}: {self.__str__()}>"
+        return f"<Assiduite {self.id}: {self.etudiant.nom}: {self.__str__()}>"
 
     @classmethod
     def create_assiduite(
diff --git a/app/models/etudiants.py b/app/models/etudiants.py
index 669a4b031b9e840dbad71869419b5c53dfb42f48..8d6e02664312af0771a6c2e7ee72d4f113984902 100644
--- a/app/models/etudiants.py
+++ b/app/models/etudiants.py
@@ -127,6 +127,12 @@ class Identite(models.ScoDocModel):
         cascade="all, delete-orphan",
         lazy="dynamic",
     )
+    notes_log = db.relationship(
+        "NotesNotesLog",
+        backref="etudiant",
+        cascade="all, delete-orphan",
+        lazy="dynamic",
+    )
     # Relations avec les assiduites et les justificatifs
     assiduites = db.relationship(
         "Assiduite", back_populates="etudiant", lazy="dynamic", cascade="all, delete"
diff --git a/app/models/evaluations.py b/app/models/evaluations.py
index cf998a56a429633b4a54808cf34976bfefda326b..a27f43090aa7af052471dfdc9ddec26e3713b8e2 100644
--- a/app/models/evaluations.py
+++ b/app/models/evaluations.py
@@ -64,6 +64,13 @@ class Evaluation(models.ScoDocModel):
         cascade="all, delete-orphan",
         lazy="dynamic",
     )
+    notes_log = db.relationship(
+        "NotesNotesLog",
+        backref="evaluation",
+        cascade="all, delete-orphan",
+        lazy="dynamic",
+        primaryjoin="Evaluation.id == foreign(NotesNotesLog.evaluation_id)",
+    )
     ues = db.relationship("UniteEns", secondary="evaluation_ue_poids", viewonly=True)
 
     _sco_dept_relations = ("ModuleImpl", "FormSemestre")  # accès au dept_id
diff --git a/app/scodoc/gen_tables.py b/app/scodoc/gen_tables.py
index 5820d20a3155eb81a42f572c012162206f4e4604..3161c9cbc9ce77a7807e1b84522289f06233cd2a 100644
--- a/app/scodoc/gen_tables.py
+++ b/app/scodoc/gen_tables.py
@@ -499,11 +499,7 @@ class GenTable:
                 H.append(caption)
             if self.base_url:
                 H.append('<span class="gt_export_icons">')
-                if self.xls_link:
-                    H.append(
-                        f""" <a href="{add_query_param(self.base_url, "fmt", "xls")
-                            }">{scu.ICON_XLS}</a>"""
-                    )
+                H.append(self.xls_export_button())
                 if self.xls_link and self.pdf_link:
                     H.append("&nbsp;")
                 if self.pdf_link:
@@ -517,6 +513,15 @@ class GenTable:
         H.append(self.html_next_section)
         return "\n".join(H)
 
+    def xls_export_button(self) -> str:
+        "markup pour export excel"
+        return (
+            f""" <a href="{add_query_param(self.base_url, "fmt", "xls")
+                }">{scu.ICON_XLS}</a>"""
+            if self.xls_link
+            else ""
+        )
+
     def excel(self, wb=None):
         """Simple Excel representation of the table"""
         if wb is None:
diff --git a/app/scodoc/sco_undo_notes.py b/app/scodoc/sco_undo_notes.py
index 9d9102b2a21cb547b5de6347ff7c3653f67ff67e..d34b84385901644e5584ba5a7df0cbdcc48260cb 100644
--- a/app/scodoc/sco_undo_notes.py
+++ b/app/scodoc/sco_undo_notes.py
@@ -46,9 +46,11 @@ Opérations:
 """
 
 import datetime
-from flask import g, request, url_for
+from flask import g, render_template, request, url_for
 
-from app.models import Evaluation, FormSemestre
+from app import db
+from app.auth.models import User
+from app.models import Evaluation, FormSemestre, ModuleImpl, NotesNotes, NotesNotesLog
 from app.scodoc.intervals import intervalmap
 import app.scodoc.sco_utils as scu
 import app.scodoc.notesdb as ndb
@@ -178,36 +180,67 @@ def evaluation_list_operations(evaluation_id: int):
     return tab.make_page()
 
 
-def formsemestre_list_saisies_notes(formsemestre_id, fmt="html"):
+def formsemestre_list_saisies_notes(formsemestre_id, only_modifs=False, fmt="html"):
     """Table listant toutes les opérations de saisies de notes, dans toutes
     les évaluations du semestre.
     """
-    formsemestre: FormSemestre = FormSemestre.get_or_404(formsemestre_id)
-    rows = ndb.SimpleDictFetch(
-        """SELECT i.nom, i.prenom, code_nip, n.*, mod.titre, e.description, e.date_debut,
-                    u.user_name, e.id as evaluation_id
-        FROM notes_notes n, notes_evaluation e, notes_moduleimpl mi,
-        notes_modules mod, identite i, "user" u
-        WHERE mi.id = e.moduleimpl_id
-        and mi.module_id = mod.id
-        and e.id = n.evaluation_id
-        and i.id = n.etudid
-        and u.id = n.uid
-        and mi.formsemestre_id = %(formsemestre_id)s
-        ORDER BY date desc
-        """,
-        {"formsemestre_id": formsemestre_id},
+    formsemestre: FormSemestre = FormSemestre.get_formsemestre(formsemestre_id)
+    only_modifs = scu.to_bool(only_modifs)
+    model = NotesNotesLog if only_modifs else NotesNotes
+    notes_query = (
+        db.session.query(model)
+        .join(Evaluation, Evaluation.id == model.evaluation_id)
+        .join(ModuleImpl)
+        .filter_by(formsemestre_id=formsemestre.id)
+        .order_by(model.date.desc())
     )
+
     # Formate les notes
     keep_numeric = fmt in scu.FORMATS_NUMERIQUES
-    for row in rows:
-        row["value"] = scu.fmt_note(row["value"], keep_numeric=keep_numeric)
-        row["date_evaluation"] = (
-            row["date_debut"].strftime("%d/%m/%Y %H:%M") if row["date_debut"] else ""
-        )
-        row["_date_evaluation_order"] = (
-            row["date_debut"].isoformat() if row["date_debut"] else ""
+    rows = []
+    for note in notes_query:
+        ens = User.get_user(note.uid)
+        evaluation = note.evaluation
+        rows.append(
+            {
+                "date": note.date.strftime(scu.DATEATIME_FMT),
+                "_date_order": note.date.isoformat(),
+                "code_nip": note.etudiant.code_nip,
+                "nom": note.etudiant.nom_disp(),
+                "prenom": note.etudiant.prenom_str,
+                "date_evaluation": (
+                    evaluation.date_debut.strftime(scu.DATEATIME_FMT)
+                    if evaluation and note.evaluation.date_debut
+                    else ""
+                ),
+                "_date_evaluation_order": (
+                    note.evaluation.date_debut.isoformat()
+                    if evaluation and note.evaluation.date_debut
+                    else ""
+                ),
+                "value": scu.fmt_note(note.value, keep_numeric=keep_numeric),
+                "module": (
+                    (
+                        note.evaluation.moduleimpl.module.code
+                        or note.evaluation.moduleimpl.module.titre
+                    )
+                    if evaluation
+                    else ""
+                ),
+                "evaluation": note.evaluation.description if evaluation else "",
+                "_evaluation_target": (
+                    url_for(
+                        "notes.evaluation_listenotes",
+                        scodoc_dept=g.scodoc_dept,
+                        evaluation_id=note.evaluation_id,
+                    )
+                    if evaluation
+                    else ""
+                ),
+                "user_name": ens.user_name if ens else "",
+            }
         )
+
     columns_ids = (
         "date",
         "code_nip",
@@ -215,9 +248,8 @@ def formsemestre_list_saisies_notes(formsemestre_id, fmt="html"):
         "prenom",
         "value",
         "user_name",
-        "titre",
-        "evaluation_id",
-        "description",
+        "module",
+        "evaluation",
         "date_evaluation",
         "comment",
     )
@@ -230,11 +262,11 @@ def formsemestre_list_saisies_notes(formsemestre_id, fmt="html"):
         "comment": "Remarque",
         "user_name": "Enseignant",
         "evaluation_id": "evaluation_id",
-        "titre": "Module",
-        "description": "Evaluation",
+        "module": "Module",
+        "evaluation": "Evaluation",
         "date_evaluation": "Date éval.",
     }
-    tab = GenTable(
+    table = GenTable(
         titles=titles,
         columns_ids=columns_ids,
         rows=rows,
@@ -244,11 +276,25 @@ def formsemestre_list_saisies_notes(formsemestre_id, fmt="html"):
         html_sortable=True,
         caption=f"Saisies de notes dans {formsemestre.titre_annee()}",
         preferences=sco_preferences.SemPreferences(formsemestre_id),
-        base_url="%s?formsemestre_id=%s" % (request.base_url, formsemestre_id),
+        base_url=f"""{request.base_url}?formsemestre_id={
+            formsemestre_id}&only_modifs={int(only_modifs)}""",
         origin=f"Généré par {sco_version.SCONAME} le " + scu.timedate_human_repr() + "",
         table_id="formsemestre_list_saisies_notes",
+        filename=(
+            f"modifs_notes_S{formsemestre.semestre_id}"
+            if only_modifs
+            else f"saisies_notes_S{formsemestre.semestre_id}"
+        ),
     )
-    return tab.make_page(fmt=fmt)
+    if fmt == "html":
+        return render_template(
+            "formsemestre/list_saisies_notes.j2",
+            table=table,
+            title="Opérations de saisies de notes",
+            only_modifs=only_modifs,
+            formsemestre_id=formsemestre.id,
+        )
+    return table.make_page(fmt=fmt, page_title="Opérations de saisies de notes")
 
 
 def get_note_history(evaluation_id, etudid, fmt=""):
diff --git a/app/templates/formsemestre/list_saisies_notes.j2 b/app/templates/formsemestre/list_saisies_notes.j2
new file mode 100644
index 0000000000000000000000000000000000000000..517d2142e51c6991efd7baf0c886b35c30b4e9db
--- /dev/null
+++ b/app/templates/formsemestre/list_saisies_notes.j2
@@ -0,0 +1,48 @@
+{% extends "sco_page.j2" %}
+
+{% block styles %}
+{{super()}}
+<style>
+.export_xls_but {
+    margin-left: 32px;
+}
+</style>
+{% endblock %}
+
+{% block app_content %}
+<h2 class="formsemestre">{{title_h2}}</h2>
+
+<div>{{table.get_nb_rows()}}
+    {% if only_modifs %}modifications{% else %}saisies{% endif %}
+    de notes dans ce semestre.
+</div>
+<form id="filter-form">
+    <label>
+        <input type="checkbox" id="only-modifs-checkbox" name="only_modifs" value="1"
+        {% if only_modifs %}checked{% endif %}>
+        Lister uniquement les modifications
+    </label>
+    <span class="export_xls_but">{{table.xls_export_button()|safe}} excel</span>
+</form>
+
+{{table.html()|safe}}
+
+
+{% endblock %}
+
+{% block scripts %}
+{{super()}}
+
+<script>
+    document.getElementById('only-modifs-checkbox').addEventListener('change', function() {
+        var form = document.getElementById('filter-form');
+        var onlyModifs = this.checked ? '1' : '0';
+
+        var url = new URL(window.location.href);
+        url.searchParams.set('formsemestre_id', {{formsemestre_id}});
+        url.searchParams.set('only_modifs', onlyModifs);
+
+        window.location.href = url.toString();
+    });
+</script>
+{% endblock %}
\ No newline at end of file
diff --git a/sco_version.py b/sco_version.py
index 8110b6d018b207f579da34c38a96f06a8940edcb..9e646dd9cdec5fcc3f22beb911c6a2ed85c192d2 100644
--- a/sco_version.py
+++ b/sco_version.py
@@ -3,7 +3,7 @@
 
 "Infos sur version ScoDoc"
 
-SCOVERSION = "9.7.39"
+SCOVERSION = "9.7.40"
 
 SCONAME = "ScoDoc"
 
diff --git a/tests/api/test_api_permissions.py b/tests/api/test_api_permissions.py
index 53393654aa73496378da51f1d43403144c22e54a..c459aeed878a6c02a1f1f7d669dd74123a64538e 100755
--- a/tests/api/test_api_permissions.py
+++ b/tests/api/test_api_permissions.py
@@ -94,6 +94,7 @@ def test_permissions(api_headers):
                 "/ScoDoc/api/justificatif/1/list",  # demande AbsJustifView
                 "/ScoDoc/api/justificatif/1/justifies",  # demande ScoJustifChange
                 "/ScoDoc/api/justificatif/1/export",  # demande AbsChange
+                "/ScoDoc/api/operations/user/",  # demande superamin ou user lui même
             ]
         ):
             # On passe ces routes spéciales