diff --git a/app/scodoc/sco_cache.py b/app/scodoc/sco_cache.py
index 4c9960dfd428244383dfa5acb3c5c24c5a5ebe78..e31b6a18d56bb32048702bae69ade601521c054c 100644
--- a/app/scodoc/sco_cache.py
+++ b/app/scodoc/sco_cache.py
@@ -121,6 +121,33 @@ class ScoDocCache:
         for oid in oids:
             cls.delete(oid)
 
+    @classmethod
+    def delete_pattern(cls, pattern: str, std_prefix=True) -> int:
+        """Delete all keys matching pattern.
+        The pattern starts with flask_cache_<dept_acronym>.
+        If std_prefix is true (default), the prefix is added
+        to the given pattern.
+        Examples:
+        'TABASSI_tableau-etud-1234:*'
+        Or, with std_prefix false, 'flask_cache_RT_TABASSI_tableau-etud-1234:*'
+
+        Returns number of keys deleted.
+        """
+        # see https://stackoverflow.com/questions/36708461/flask-cache-list-keys-based-on-a-pattern
+        assert CACHE.cache.__class__.__name__ == "RedisCache"  # Redis specific
+        import redis
+
+        if std_prefix:
+            pattern = "flask_cache_" + g.scodoc_dept + "_" + cls.prefix + "_" + pattern
+
+        r = redis.Redis()
+        count = 0
+        for key in r.scan_iter(pattern):
+            log(f"{cls.__name__}.delete_pattern({key})")
+            r.delete(key)
+            count += 1
+        return count
+
 
 class EvaluationCache(ScoDocCache):
     """Cache for evaluations.
diff --git a/sco_version.py b/sco_version.py
index 53da13f95d704e321637b5c601365fc0cb35b566..7b986ac6c91816af6ea7a9c73efe6e54bed77d5e 100644
--- a/sco_version.py
+++ b/sco_version.py
@@ -1,7 +1,7 @@
 # -*- mode: python -*-
 # -*- coding: utf-8 -*-
 
-SCOVERSION = "9.6.77"
+SCOVERSION = "9.6.78"
 
 SCONAME = "ScoDoc"
 
diff --git a/tests/unit/test_caches.py b/tests/unit/test_caches.py
index 3a4882d778bda2de4b9c5759be9352fd00c653f5..0e3766ba04e3c374696b809cc55ecb145988c7b7 100644
--- a/tests/unit/test_caches.py
+++ b/tests/unit/test_caches.py
@@ -48,6 +48,10 @@ def test_notes_table(test_client):  # XXX A REVOIR POUR TESTER RES TODO
         formsemestre_id = sem["formsemestre_id"]
         nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
         assert sco_cache.ResultatsSemestreCache.get(formsemestre_id)
+    # Efface les semestres
+    sco_cache.ResultatsSemestreCache.delete_pattern("*")
+    for sem in sems[:10]:
+        assert sco_cache.ResultatsSemestreCache.get(formsemestre_id) is None
 
 
 def test_cache_evaluations(test_client):