diff --git a/app/api/departements.py b/app/api/departements.py
index 787193e232f15947da0053171dae95eac1c2549b..5965a03a1d74706d75d2e0716bcfa80a5077e82e 100644
--- a/app/api/departements.py
+++ b/app/api/departements.py
@@ -96,7 +96,7 @@ def departement_get(dept_id: int):
     /departement/id/1;
 
     """
-    dept = Departement.query.get_or_404(dept_id)
+    dept = Departement.get_or_404(dept_id)
     return dept.to_dict()
 
 
@@ -212,7 +212,7 @@ def departement_etudiants_by_id(dept_id: int):
     """
     Retourne la liste des étudiants d'un département d'id donné.
     """
-    dept = Departement.query.get_or_404(dept_id)
+    dept = Departement.get_or_404(dept_id)
     return [etud.to_dict_short() for etud in dept.etudiants]
 
 
@@ -246,7 +246,7 @@ def departement_formsemestres_ids_by_id(dept_id: int):
     /departement/id/1/formsemestres_ids;
 
     """
-    dept = Departement.query.get_or_404(dept_id)
+    dept = Departement.get_or_404(dept_id)
     return [formsemestre.id for formsemestre in dept.formsemestres]
 
 
@@ -273,7 +273,7 @@ def departement_formsemestres_courants(acronym: str = "", dept_id: int | None =
     dept = (
         Departement.query.filter_by(acronym=acronym).first_or_404()
         if acronym
-        else Departement.query.get_or_404(dept_id)
+        else Departement.get_or_404(dept_id)
     )
     date_courante = request.args.get("date_courante")
     date_courante = datetime.fromisoformat(date_courante) if date_courante else None
diff --git a/app/api/evaluations.py b/app/api/evaluations.py
index 498d886cc5d23860ad85812d3f69f76decdddda5..36d48abcbc8237c0cb572ab7eb152775d2ef8cba 100644
--- a/app/api/evaluations.py
+++ b/app/api/evaluations.py
@@ -215,7 +215,7 @@ def evaluation_create(moduleimpl_id: int):
     /moduleimpl/1/evaluation/create;{""description"":""Exemple éval.""}
 
     """
-    moduleimpl: ModuleImpl = ModuleImpl.query.get_or_404(moduleimpl_id)
+    moduleimpl: ModuleImpl = ModuleImpl.get_or_404(moduleimpl_id)
     if not moduleimpl.can_edit_evaluation(current_user):
         return scu.json_error(403, "opération non autorisée")
     data = request.get_json(force=True)  # may raise 400 Bad Request
diff --git a/app/api/formations.py b/app/api/formations.py
index d2de7ce2e97ff76df18721d30d8b3a2dcb0c0aed..e618716b0faf1cae9302dca2ab78cbbf9e049dfb 100644
--- a/app/api/formations.py
+++ b/app/api/formations.py
@@ -199,7 +199,7 @@ def ue_set_parcours(ue_id: int):
         parcours = []
     else:
         parcours = [
-            ApcParcours.query.get_or_404(int(parcour_id)) for parcour_id in parcours_ids
+            ApcParcours.get_or_404(int(parcour_id)) for parcour_id in parcours_ids
         ]
     log(f"ue_set_parcours: ue_id={ue.id} parcours_ids={parcours_ids}")
     ok, error_message = ue.set_parcours(parcours)
@@ -226,7 +226,7 @@ def ue_assoc_niveau(ue_id: int, niveau_id: int):
     if g.scodoc_dept:
         query = query.join(Formation).filter_by(dept_id=g.scodoc_dept_id)
     ue: UniteEns = query.first_or_404()
-    niveau: ApcNiveau = ApcNiveau.query.get_or_404(niveau_id)
+    niveau: ApcNiveau = ApcNiveau.get_or_404(niveau_id)
     ok, error_message = ue.set_niveau_competence(niveau)
     if not ok:
         if g.scodoc_dept:  # "usage web"
diff --git a/app/api/formsemestres.py b/app/api/formsemestres.py
index afd2e9bade79d652186a51c332a0cc6ec54db428..4d91bc4f5168053f9b249801018ea588f01e9d01 100644
--- a/app/api/formsemestres.py
+++ b/app/api/formsemestres.py
@@ -615,13 +615,13 @@ def formsemestre_etat_evaluations(formsemestre_id: int):
     result = []
     for modimpl_id in nt.modimpls_results:
         modimpl_results: ModuleImplResults = nt.modimpls_results[modimpl_id]
-        modimpl: ModuleImpl = ModuleImpl.query.get_or_404(modimpl_id)
+        modimpl: ModuleImpl = ModuleImpl.get_or_404(modimpl_id)
         modimpl_dict = modimpl.to_dict(convert_objects=True, with_module=False)
 
         list_eval = []
         for evaluation_id in modimpl_results.evaluations_etat:
             eval_etat = modimpl_results.evaluations_etat[evaluation_id]
-            evaluation = Evaluation.query.get_or_404(evaluation_id)
+            evaluation = Evaluation.get_or_404(evaluation_id)
             eval_dict = evaluation.to_dict_api()
             eval_dict["etat"] = eval_etat.to_dict()
 
diff --git a/app/api/jury.py b/app/api/jury.py
index 528faa435c00f75dd9069194f8e2e114fc838a7e..8005910b6e5e7dcdf383f205048bc11ac234670d 100644
--- a/app/api/jury.py
+++ b/app/api/jury.py
@@ -277,7 +277,7 @@ def validation_rcue_record(etudid: int):
     except ValueError:
         return json_error(API_CLIENT_ERROR, "invalid date string")
     if parcours_id is not None:
-        parcours: ApcParcours = ApcParcours.query.get_or_404(parcours_id)
+        parcours: ApcParcours = ApcParcours.get_or_404(parcours_id)
         if parcours.referentiel_id != ue1.niveau_competence.competence.referentiel_id:
             return json_error(API_CLIENT_ERROR, "niveau et parcours incompatibles")
 
diff --git a/app/api/partitions.py b/app/api/partitions.py
index d9ac1cef10e31d54194e9d943efdd40ceab3fb12..9008285fa206a05c8ec3d3a06bca6752614c9b6b 100644
--- a/app/api/partitions.py
+++ b/app/api/partitions.py
@@ -159,7 +159,7 @@ def group_etudiants_query(group_id: int):
 @as_json
 def group_set_etudiant(group_id: int, etudid: int):
     """Affecte l'étudiant au groupe indiqué."""
-    etud = Identite.query.get_or_404(etudid)
+    etud = Identite.get_or_404(etudid)
     query = GroupDescr.query.filter_by(id=group_id)
     if g.scodoc_dept:
         query = (
@@ -192,7 +192,7 @@ def group_set_etudiant(group_id: int, etudid: int):
 @as_json
 def group_remove_etud(group_id: int, etudid: int):
     """Retire l'étudiant de ce groupe. S'il n'y est pas, ne fait rien."""
-    etud = Identite.query.get_or_404(etudid)
+    etud = Identite.get_or_404(etudid)
     query = GroupDescr.query.filter_by(id=group_id)
     if g.scodoc_dept:
         query = (
@@ -224,7 +224,7 @@ def partition_remove_etud(partition_id: int, etudid: int):
 
     (NB: en principe, un étudiant ne doit être que dans 0 ou 1 groupe d'une partition)
     """
-    etud = Identite.query.get_or_404(etudid)
+    etud = Identite.get_or_404(etudid)
     query = Partition.query.filter_by(id=partition_id)
     if g.scodoc_dept:
         query = query.join(FormSemestre).filter_by(dept_id=g.scodoc_dept_id)
@@ -533,7 +533,7 @@ def formsemestre_set_partitions_order(formsemestre_id: int):
             message="paramètre liste des partitions invalide",
         )
     for p_id, numero in zip(partition_ids, range(len(partition_ids))):
-        partition = Partition.query.get_or_404(p_id)
+        partition = Partition.get_or_404(p_id)
         partition.numero = numero
         db.session.add(partition)
     db.session.commit()
@@ -579,7 +579,7 @@ def partition_order_groups(partition_id: int):
             message="paramètre liste de groupe invalide",
         )
     for group_id, numero in zip(group_ids, range(len(group_ids))):
-        group = GroupDescr.query.get_or_404(group_id)
+        group = GroupDescr.get_or_404(group_id)
         group.numero = numero
         db.session.add(group)
     db.session.commit()
diff --git a/app/api/users.py b/app/api/users.py
index f446c72792ab3f283b75b630c6132839f0cc0116..d5cb2f7acae410e9c75464dbe389b1c3962f7d95 100644
--- a/app/api/users.py
+++ b/app/api/users.py
@@ -188,7 +188,7 @@ def user_edit(uid: int):
     ```
     """
     args = request.get_json(force=True)  # may raise 400 Bad Request
-    user: User = User.query.get_or_404(uid)
+    user: User = User.get_or_404(uid)
     # L'utilisateur doit avoir le droit dans le département de départ et celui d'arrivée
     orig_dept = user.dept
     dest_dept = args.get("dept", False)
@@ -241,7 +241,7 @@ def user_password(uid: int):
     /user/3/password;{""password"" : ""rePlaCemeNT456averylongandcomplicated""}
     """
     data = request.get_json(force=True)  # may raise 400 Bad Request
-    user: User = User.query.get_or_404(uid)
+    user: User = User.get_or_404(uid)
     password = data.get("password")
     if not password:
         return json_error(404, "user_password: missing password")
@@ -272,7 +272,7 @@ def user_password(uid: int):
 @as_json
 def user_role_add(uid: int, role_name: str, dept: str = None):
     """Ajoute un rôle à l'utilisateur dans le département donné."""
-    user: User = User.query.get_or_404(uid)
+    user: User = User.get_or_404(uid)
     role: Role = Role.query.filter_by(name=role_name).first_or_404()
     if dept is not None:  # check
         _ = Departement.query.filter_by(acronym=dept).first_or_404()
@@ -301,7 +301,7 @@ def user_role_add(uid: int, role_name: str, dept: str = None):
 @as_json
 def user_role_remove(uid: int, role_name: str, dept: str = None):
     """Retire le rôle (dans le département donné) à cet utilisateur."""
-    user: User = User.query.get_or_404(uid)
+    user: User = User.get_or_404(uid)
     role: Role = Role.query.filter_by(name=role_name).first_or_404()
     if dept is not None:  # check
         _ = Departement.query.filter_by(acronym=dept).first_or_404()
diff --git a/app/auth/models.py b/app/auth/models.py
index 465169c578b42ab983245a130fdab7ab685ab9e3..ba1c6701a17ef321ee6661958c1a182a0ab05cb1 100644
--- a/app/auth/models.py
+++ b/app/auth/models.py
@@ -626,7 +626,7 @@ class AnonymousUser(AnonymousUserMixin):
 login.anonymous_user = AnonymousUser
 
 
-class Role(db.Model):
+class Role(ScoDocModel):
     """Roles for ScoDoc"""
 
     id = db.Column(db.Integer, primary_key=True)
@@ -730,7 +730,7 @@ class Role(db.Model):
         return Role.query.filter_by(name=name).first()
 
 
-class UserRole(db.Model):
+class UserRole(ScoDocModel):
     """Associate user to role, in a dept.
     If dept is None, the role applies to all departments (eg super admin).
     """
diff --git a/app/but/bulletin_but_court.py b/app/but/bulletin_but_court.py
index d9772d63056d5dd44e5ae5cbd6e068cd5531326d..0254d102beeca723c1cc1bc68084e5373df6ea78 100644
--- a/app/but/bulletin_but_court.py
+++ b/app/but/bulletin_but_court.py
@@ -57,7 +57,7 @@ from app.views import ScoData
 @permission_required(Permission.ScoView)
 def bulletin_but(formsemestre_id: int, etudid: int = None, fmt="html"):
     """Page HTML affichant le bulletin BUT simplifié"""
-    etud: Identite = Identite.query.get_or_404(etudid)
+    etud: Identite = Identite.get_or_404(etudid)
     formsemestre: FormSemestre = (
         FormSemestre.query.filter_by(id=formsemestre_id)
         .join(FormSemestreInscription)
diff --git a/app/but/cursus_but.py b/app/but/cursus_but.py
index 6afa8f82ed6cd7a6b2a63792c29b5519d31355e7..b321cc46b4003430749d65f244927313cc588aa2 100644
--- a/app/but/cursus_but.py
+++ b/app/but/cursus_but.py
@@ -508,7 +508,7 @@ def but_validations_ues_parcours(
     # Les UEs associées au tronc commun (à aucun parcours)
     # UniteEns.query.filter(~UniteEns.id.in_(UEParcours.query.with_entities(UEParcours.ue_id)))
 
-    parcour = ApcParcours.query.get(parcour_id)
+    parcour: ApcParcours = db.session.get(ApcParcours, parcour_id)
     if not parcour:
         raise ScoValueError(f"but_validations_ues_parcours: {parcour_id} inexistant")
     # Les validations d'UE de ce parcours ou du tronc commun pour cet étudiant:
diff --git a/app/entreprises/models.py b/app/entreprises/models.py
index b41d6b5ce36a826eb9743741fb49f8f3c88352f7..91f31f3fca09388e3e45f4ade444ff2422954162 100644
--- a/app/entreprises/models.py
+++ b/app/entreprises/models.py
@@ -1,7 +1,7 @@
-from app import db
+from app import db, models
 
 
-class Entreprise(db.Model):
+class Entreprise(models.ScoDocModel):
     __tablename__ = "are_entreprises"
     id = db.Column(db.Integer, primary_key=True)
     siret = db.Column(db.Text, index=True, unique=True)
@@ -45,7 +45,7 @@ class Entreprise(db.Model):
         }
 
 
-class EntrepriseSite(db.Model):
+class EntrepriseSite(models.ScoDocModel):
     __tablename__ = "are_sites"
     id = db.Column(db.Integer, primary_key=True)
     entreprise_id = db.Column(
@@ -65,7 +65,7 @@ class EntrepriseSite(db.Model):
     )
 
     def to_dict(self):
-        entreprise = Entreprise.query.get_or_404(self.entreprise_id)
+        entreprise = Entreprise.get_or_404(self.entreprise_id)
         return {
             "siret_entreprise": entreprise.siret,
             "id_site": self.id,
@@ -77,7 +77,7 @@ class EntrepriseSite(db.Model):
         }
 
 
-class EntrepriseCorrespondant(db.Model):
+class EntrepriseCorrespondant(models.ScoDocModel):
     __tablename__ = "are_correspondants"
     id = db.Column(db.Integer, primary_key=True)
     site_id = db.Column(db.Integer, db.ForeignKey("are_sites.id", ondelete="cascade"))
@@ -92,7 +92,7 @@ class EntrepriseCorrespondant(db.Model):
     notes = db.Column(db.Text)
 
     def to_dict(self):
-        site = EntrepriseSite.query.get_or_404(self.site_id)
+        site = EntrepriseSite.get_or_404(self.site_id)
         return {
             "id": self.id,
             "civilite": self.civilite,
@@ -108,7 +108,7 @@ class EntrepriseCorrespondant(db.Model):
         }
 
 
-class EntrepriseContact(db.Model):
+class EntrepriseContact(models.ScoDocModel):
     __tablename__ = "are_contacts"
     id = db.Column(db.Integer, primary_key=True)
     date = db.Column(db.DateTime(timezone=True))
@@ -119,7 +119,7 @@ class EntrepriseContact(db.Model):
     notes = db.Column(db.Text)
 
 
-class EntrepriseOffre(db.Model):
+class EntrepriseOffre(models.ScoDocModel):
     __tablename__ = "are_offres"
     id = db.Column(db.Integer, primary_key=True)
     entreprise_id = db.Column(
@@ -147,7 +147,7 @@ class EntrepriseOffre(db.Model):
         }
 
 
-class EntrepriseHistorique(db.Model):
+class EntrepriseHistorique(models.ScoDocModel):
     __tablename__ = "are_historique"
     id = db.Column(db.Integer, primary_key=True)
     date = db.Column(db.DateTime(timezone=True), server_default=db.func.now())
@@ -158,7 +158,7 @@ class EntrepriseHistorique(db.Model):
     text = db.Column(db.Text)
 
 
-class EntrepriseStageApprentissage(db.Model):
+class EntrepriseStageApprentissage(models.ScoDocModel):
     __tablename__ = "are_stages_apprentissages"
     id = db.Column(db.Integer, primary_key=True)
     entreprise_id = db.Column(
@@ -176,7 +176,7 @@ class EntrepriseStageApprentissage(db.Model):
     notes = db.Column(db.Text)
 
 
-class EntrepriseTaxeApprentissage(db.Model):
+class EntrepriseTaxeApprentissage(models.ScoDocModel):
     __tablename__ = "are_taxe_apprentissage"
     id = db.Column(db.Integer, primary_key=True)
     entreprise_id = db.Column(
@@ -187,7 +187,7 @@ class EntrepriseTaxeApprentissage(db.Model):
     notes = db.Column(db.Text)
 
 
-class EntrepriseEnvoiOffre(db.Model):
+class EntrepriseEnvoiOffre(models.ScoDocModel):
     __tablename__ = "are_envoi_offre"
     id = db.Column(db.Integer, primary_key=True)
     sender_id = db.Column(db.Integer, db.ForeignKey("user.id", ondelete="cascade"))
@@ -196,7 +196,7 @@ class EntrepriseEnvoiOffre(db.Model):
     date_envoi = db.Column(db.DateTime(timezone=True), server_default=db.func.now())
 
 
-class EntrepriseEnvoiOffreEtudiant(db.Model):
+class EntrepriseEnvoiOffreEtudiant(models.ScoDocModel):
     __tablename__ = "are_envoi_offre_etudiant"
     id = db.Column(db.Integer, primary_key=True)
     sender_id = db.Column(db.Integer, db.ForeignKey("user.id", ondelete="cascade"))
@@ -207,14 +207,14 @@ class EntrepriseEnvoiOffreEtudiant(db.Model):
     date_envoi = db.Column(db.DateTime(timezone=True), server_default=db.func.now())
 
 
-class EntrepriseOffreDepartement(db.Model):
+class EntrepriseOffreDepartement(models.ScoDocModel):
     __tablename__ = "are_offre_departement"
     id = db.Column(db.Integer, primary_key=True)
     offre_id = db.Column(db.Integer, db.ForeignKey("are_offres.id", ondelete="cascade"))
     dept_id = db.Column(db.Integer, db.ForeignKey("departement.id", ondelete="cascade"))
 
 
-class EntreprisePreferences(db.Model):
+class EntreprisePreferences(models.ScoDocModel):
     __tablename__ = "are_preferences"
     id = db.Column(db.Integer, primary_key=True)
     name = db.Column(db.Text)
diff --git a/app/entreprises/routes.py b/app/entreprises/routes.py
index 537e86b96419dfcae772723df6c309de043dffa9..e5696adf4825b1b1d255e0f96d0ca490db75cc20 100644
--- a/app/entreprises/routes.py
+++ b/app/entreprises/routes.py
@@ -1489,7 +1489,7 @@ def add_stage_apprentissage(entreprise_id):
         )
     if form.validate_on_submit():
         etudid = form.etudid.data
-        etudiant = Identite.query.get_or_404(etudid)
+        etudiant = Identite.get_or_404(etudid)
         formation = etudiant.inscription_courante_date(
             form.date_debut.data, form.date_fin.data
         )
@@ -1552,7 +1552,7 @@ def edit_stage_apprentissage(entreprise_id, stage_apprentissage_id):
         )
     if form.validate_on_submit():
         etudid = form.etudid.data
-        etudiant = Identite.query.get_or_404(etudid)
+        etudiant = Identite.get_or_404(etudid)
         formation = etudiant.inscription_courante_date(
             form.date_debut.data, form.date_fin.data
         )
diff --git a/app/formations/edit_formation.py b/app/formations/edit_formation.py
index 3b9a263689c4d7492b4d410da068ccc59bd0105e..593199a09adc826d6acb007f8b48b37019b512ef 100644
--- a/app/formations/edit_formation.py
+++ b/app/formations/edit_formation.py
@@ -50,7 +50,7 @@ from app.scodoc import codes_cursus
 
 def formation_delete(formation_id=None, dialog_confirmed=False):
     """Delete a formation"""
-    formation: Formation = Formation.query.get_or_404(formation_id)
+    formation: Formation = Formation.get_or_404(formation_id)
 
     H = [
         f"""<h2>Suppression de la formation {formation.titre} ({formation.acronyme})</h2>""",
@@ -159,7 +159,7 @@ def formation_edit(formation_id=None, create=False):
         is_locked = False
     else:
         # edit an existing formation
-        formation: Formation = Formation.query.get_or_404(formation_id)
+        formation: Formation = Formation.get_or_404(formation_id)
         form_dict = formation.to_dict()
         form_dict["commentaire"] = form_dict["commentaire"] or ""
         initvalues = form_dict
@@ -347,7 +347,7 @@ def do_formation_edit(args) -> bool:
     if "formation_code" in args and not args["formation_code"]:
         del args["formation_code"]
 
-    formation: Formation = Formation.query.get_or_404(args["formation_id"])
+    formation: Formation = Formation.get_or_404(args["formation_id"])
     # On autorise  la modif de la formation meme si elle est verrouillee
     # car cela ne change que du cosmetique, (sauf eventuellement le code formation ?)
     # mais si verrouillée on ne peut changer le type de parcours
@@ -386,7 +386,7 @@ def do_formation_edit(args) -> bool:
 def module_move(module_id, after=0, redirect=True):
     """Move before/after previous one (decrement/increment numero)"""
     redirect = bool(redirect)
-    module = Module.query.get_or_404(module_id)
+    module = Module.get_or_404(module_id)
     after = int(after)  # 0: deplace avant, 1 deplace apres
     if after not in (0, 1):
         raise ValueError(f'invalid value for "after" ({after})')
@@ -430,7 +430,7 @@ def module_move(module_id, after=0, redirect=True):
 
 def ue_move(ue_id, after=0, redirect=1):
     """Move UE before/after previous one (decrement/increment numero)"""
-    ue = UniteEns.query.get_or_404(ue_id)
+    ue = UniteEns.get_or_404(ue_id)
     redirect = int(redirect)
     after = int(after)  # 0: deplace avant, 1 deplace apres
     if after not in (0, 1):
diff --git a/app/formations/edit_matiere.py b/app/formations/edit_matiere.py
index d758ee1959dc8b884313f09b10cab60f4f38a7bc..517e3570765adfa39567f267e2ee91481d2af2e0 100644
--- a/app/formations/edit_matiere.py
+++ b/app/formations/edit_matiere.py
@@ -45,7 +45,7 @@ from app.scodoc.sco_exceptions import (
 
 def matiere_create(ue_id=None):
     """Formulaire création d'une matiere"""
-    ue: UniteEns = UniteEns.query.get_or_404(ue_id)
+    ue: UniteEns = UniteEns.get_or_404(ue_id)
     default_numero = max([mat.numero for mat in ue.matieres] or [9]) + 1
     H = [
         f"""<h2>Création d'une matière dans l'UE {ue.titre or ''} ({ue.acronyme})</h2>
diff --git a/app/formations/edit_module.py b/app/formations/edit_module.py
index 34673579dae425062cc584b3c91483cd77f497f9..3885527744bd3ed624841c20e594c93938d9abf5 100644
--- a/app/formations/edit_module.py
+++ b/app/formations/edit_module.py
@@ -52,7 +52,7 @@ from app.scodoc import codes_cursus
 
 def module_delete(module_id=None):
     """Formulaire suppression d'un module"""
-    module = Module.query.get_or_404(module_id)
+    module = Module.get_or_404(module_id)
 
     if not module.can_be_deleted():
         raise ScoNonEmptyFormationObject(
@@ -149,13 +149,13 @@ def module_edit(
             formation = ue.formation
             orig_semestre_idx = ue.semestre_idx if semestre_id is None else semestre_id
         else:
-            formation = Formation.query.get_or_404(formation_id)
+            formation = Formation.get_or_404(formation_id)
         module = None
         unlocked = True
     else:
         if not module_id:
             raise ValueError("missing module_id !")
-        module = models.Module.query.get_or_404(module_id)
+        module = models.Module.get_or_404(module_id)
         ue = module.ue
         module_dict = module.to_dict()
         formation = module.formation
@@ -714,7 +714,7 @@ def module_edit(
         old_ue_id = module.ue.id
         new_ue_id = tf[2]["ue_id"]
         if (old_ue_id != new_ue_id) and in_use:
-            new_ue = UniteEns.query.get_or_404(new_ue_id)
+            new_ue = UniteEns.get_or_404(new_ue_id)
             if new_ue.semestre_idx != module.ue.semestre_idx:
                 # pas changer de semestre un module utilisé !
                 raise ScoValueError(
@@ -808,7 +808,7 @@ def formation_add_malus_modules(
 ):
     """Création d'un module de "malus" dans chaque UE d'une formation"""
 
-    formation = Formation.query.get_or_404(formation_id)
+    formation = Formation.get_or_404(formation_id)
 
     nb = 0
     ues = formation.ues
diff --git a/app/formations/edit_ue.py b/app/formations/edit_ue.py
index e02edf607c360c5f56305aed2c7b6a46c648b645..4c77666af701f4550173965bf5e80c8795d64d18 100644
--- a/app/formations/edit_ue.py
+++ b/app/formations/edit_ue.py
@@ -219,7 +219,7 @@ def ue_edit(ue_id=None, create=False, formation_id=None, default_semestre_idx=No
     """Formulaire modification ou création d'une UE"""
     create = int(create)
     if not create:
-        ue: UniteEns = UniteEns.query.get_or_404(ue_id)
+        ue: UniteEns = UniteEns.get_or_404(ue_id)
         ue_dict = ue.to_dict()
         formation_id = ue.formation_id
         title = f"Modification de l'UE {ue.acronyme} {ue.titre}"
@@ -573,7 +573,7 @@ def next_ue_numero(formation_id, semestre_id=None) -> int:
 
 def ue_delete(ue_id=None, delete_validations=False, dialog_confirmed=False):
     """Delete an UE"""
-    ue = UniteEns.query.get_or_404(ue_id)
+    ue = UniteEns.get_or_404(ue_id)
     if ue.modules.all():
         raise ScoValueError(
             f"""Suppression de l'UE {ue.titre} impossible car
@@ -1370,7 +1370,7 @@ def ue_sharing_code(ue_code: str = "", ue_id: int = None, hide_ue_id: int = None
     hide_ue_id spécifie un id à retirer de la liste.
     """
     if ue_id is not None:
-        ue = UniteEns.query.get_or_404(ue_id)
+        ue = UniteEns.get_or_404(ue_id)
         if not ue_code:
             ue_code = ue.ue_code
         formation_code = ue.formation.formation_code
diff --git a/app/formations/formation_io.py b/app/formations/formation_io.py
index 38fc53d13000ad0f2d87ce7a249042f47894b539..5f3b1e1b8ad8b6356c48347505a90bb103875d19 100644
--- a/app/formations/formation_io.py
+++ b/app/formations/formation_io.py
@@ -653,7 +653,7 @@ def formation_list_table(detail: bool) -> GenTable:
 
 def formation_create_new_version(formation_id, redirect=True):
     "duplicate formation, with new version number"
-    formation = Formation.query.get_or_404(formation_id)
+    formation = Formation.get_or_404(formation_id)
     resp = formation_export(
         formation_id, export_ids=True, export_external_ues=True, fmt="xml"
     )
diff --git a/app/formations/formation_versions.py b/app/formations/formation_versions.py
index afc41b293dde346b5193f76bf0aa649ec85a1f67..41596aaf0b1ded41789b40d65691d6e84a002d6f 100644
--- a/app/formations/formation_versions.py
+++ b/app/formations/formation_versions.py
@@ -60,7 +60,7 @@ def formsemestre_associate_new_version(
     formsemestre_id: optionnel, formsemestre de départ, qui sera associé à la nouvelle version
     """
     formsemestre_id = int(formsemestre_id) if formsemestre_id else None
-    formation: Formation = Formation.query.get_or_404(formation_id)
+    formation: Formation = Formation.get_or_404(formation_id)
     other_formsemestre_ids = {int(x) for x in (other_formsemestre_ids or [])}
     if request.method == "GET":
         # dresse la liste des semestres non verrouillés de la même formation
@@ -191,7 +191,7 @@ def do_formsemestres_associate_new_version(
     log(f"do_formsemestres_associate_new_version {formation_id} {formsemestre_ids}")
 
     # Check: tous les semestres de la formation
-    formsemestres = [FormSemestre.query.get_or_404(i) for i in formsemestre_ids]
+    formsemestres = [FormSemestre.get_or_404(i) for i in formsemestre_ids]
     if not all(
         [formsemestre.formation_id == formation_id for formsemestre in formsemestres]
     ):
diff --git a/app/models/__init__.py b/app/models/__init__.py
index c70d2fbdf67b7138d2f95b7890553ad6859a222b..0ca9a70cd38eebfe2892c320b0fcd719d1c3e57d 100644
--- a/app/models/__init__.py
+++ b/app/models/__init__.py
@@ -5,6 +5,8 @@
 
 from flask import abort, g
 import sqlalchemy
+from sqlalchemy import select
+from sqlalchemy.exc import NoResultFound
 import app
 from app import db
 
@@ -164,6 +166,16 @@ class ScoDocModel(db.Model):
             return query.first()
         return query.first_or_404()
 
+    # Compatibilité avec SQLAlchemy 2.0
+    @classmethod
+    def get_or_404(cls, oid: int | str):
+        """Get instance or abort 404"""
+        stmt = select(cls).where(cls.id == oid)
+        try:
+            return db.session.execute(stmt).scalar_one()
+        except NoResultFound:
+            abort(404)
+
 
 from app.models.absences import Absence, AbsenceNotification, BilletAbsence
 from app.models.departements import Departement
diff --git a/app/models/absences.py b/app/models/absences.py
index 27aab114b32137f63c01a9f6cca2f74a5b402fce..c3265a7711b1e1534562a7278bf551b50b3f5e3e 100644
--- a/app/models/absences.py
+++ b/app/models/absences.py
@@ -3,10 +3,10 @@
 """Gestion des absences
 """
 
-from app import db
+from app import db, models
 
 
-class Absence(db.Model):
+class Absence(models.ScoDocModel):
     """LEGACY
     Ce modèle n'est PLUS UTILISE depuis ScoDoc 9.6 et remplacé par assiduité.
     une absence (sur une demi-journée)
@@ -45,7 +45,7 @@ class Absence(db.Model):
         return data
 
 
-class AbsenceNotification(db.Model):
+class AbsenceNotification(models.ScoDocModel):
     """Notification d'absence émise"""
 
     __tablename__ = "absences_notifications"
@@ -67,7 +67,7 @@ class AbsenceNotification(db.Model):
     )
 
 
-class BilletAbsence(db.Model):
+class BilletAbsence(models.ScoDocModel):
     """Billet d'absence (signalement par l'étudiant)"""
 
     __tablename__ = "billet_absence"
diff --git a/app/models/assiduites.py b/app/models/assiduites.py
index 5c36a1710e862570682af5d7ea1560577d985e8d..44cf1fc1e7e83dc0e354ddb1fb24c28d1cf5a0d0 100644
--- a/app/models/assiduites.py
+++ b/app/models/assiduites.py
@@ -752,7 +752,7 @@ def get_assiduites_justif(assiduite_id: int, long: bool) -> list[int | dict]:
         list[int | dict]: La liste des justificatifs (par défaut uniquement
             les identifiants, sinon les dict si long est vrai)
     """
-    assi: Assiduite = Assiduite.query.get_or_404(assiduite_id)
+    assi: Assiduite = Assiduite.get_or_404(assiduite_id)
     return get_justifs_from_date(assi.etudid, assi.date_debut, assi.date_fin, long)
 
 
diff --git a/app/models/but_refcomp.py b/app/models/but_refcomp.py
index f072bfb6c79b8debefdfcfc80e263a9a7de1241b..d89e19dab4cb1ab3113c8982f7cb73f98ca0244c 100644
--- a/app/models/but_refcomp.py
+++ b/app/models/but_refcomp.py
@@ -15,7 +15,7 @@ from flask_sqlalchemy.query import Query
 from sqlalchemy.orm import class_mapper
 import sqlalchemy
 
-from app import db, log
+from app import db, log, models
 
 from app.scodoc.sco_utils import ModuleType
 from app.scodoc.sco_exceptions import ScoNoReferentielCompetences, ScoValueError
@@ -56,7 +56,7 @@ class XMLModel:
         return f'<{self.__class__.__name__} {self.id} "{self.titre if hasattr(self, "titre") else ""}">'
 
 
-class ApcReferentielCompetences(db.Model, XMLModel):
+class ApcReferentielCompetences(models.ScoDocModel, XMLModel):
     "Référentiel de compétence d'une spécialité"
     id = db.Column(db.Integer, primary_key=True)
     dept_id = db.Column(
@@ -339,7 +339,7 @@ class ApcReferentielCompetences(db.Model, XMLModel):
         return doc.get(self.specialite, {})
 
 
-class ApcCompetence(db.Model, XMLModel):
+class ApcCompetence(models.ScoDocModel, XMLModel):
     "Compétence"
     id = db.Column(db.Integer, primary_key=True)
     referentiel_id = db.Column(
@@ -410,7 +410,7 @@ class ApcCompetence(db.Model, XMLModel):
         }
 
 
-class ApcSituationPro(db.Model, XMLModel):
+class ApcSituationPro(models.ScoDocModel, XMLModel):
     "Situation professionnelle"
     id = db.Column(db.Integer, primary_key=True)
     competence_id = db.Column(
@@ -425,7 +425,7 @@ class ApcSituationPro(db.Model, XMLModel):
         return {"libelle": self.libelle}
 
 
-class ApcComposanteEssentielle(db.Model, XMLModel):
+class ApcComposanteEssentielle(models.ScoDocModel, XMLModel):
     "Composante essentielle"
     id = db.Column(db.Integer, primary_key=True)
     competence_id = db.Column(
@@ -439,7 +439,7 @@ class ApcComposanteEssentielle(db.Model, XMLModel):
         return {"libelle": self.libelle}
 
 
-class ApcNiveau(db.Model, XMLModel):
+class ApcNiveau(models.ScoDocModel, XMLModel):
     """Niveau de compétence
     Chaque niveau peut être associé à deux UE,
     des semestres impair et pair de la même année.
@@ -608,7 +608,7 @@ app_critiques_modules = db.Table(
 )
 
 
-class ApcAppCritique(db.Model, XMLModel):
+class ApcAppCritique(models.ScoDocModel, XMLModel):
     "Apprentissage Critique BUT"
     id = db.Column(db.Integer, primary_key=True)
     niveau_id = db.Column(
@@ -694,7 +694,7 @@ parcours_formsemestre = db.Table(
 """Association parcours <-> formsemestre (many-to-many)"""
 
 
-class ApcParcours(db.Model, XMLModel):
+class ApcParcours(models.ScoDocModel, XMLModel):
     "Un parcours BUT"
     id = db.Column(db.Integer, primary_key=True)
     referentiel_id = db.Column(
@@ -749,7 +749,7 @@ class ApcParcours(db.Model, XMLModel):
         )
 
 
-class ApcAnneeParcours(db.Model, XMLModel):
+class ApcAnneeParcours(models.ScoDocModel, XMLModel):
     id = db.Column(db.Integer, primary_key=True)
     parcours_id = db.Column(
         db.Integer, db.ForeignKey("apc_parcours.id", ondelete="CASCADE"), nullable=False
@@ -774,7 +774,7 @@ class ApcAnneeParcours(db.Model, XMLModel):
         }
 
 
-class ApcParcoursNiveauCompetence(db.Model):
+class ApcParcoursNiveauCompetence(models.ScoDocModel):
     """Association entre année de parcours et compétence.
     Le "niveau" de la compétence est donné ici
     (convention Orébut)
diff --git a/app/models/config.py b/app/models/config.py
index 25d9b9ccb7c9eaa0b1d70c1691742da48de45e0e..f137ccccbaadc919b33eada0d19495e545734708 100644
--- a/app/models/config.py
+++ b/app/models/config.py
@@ -8,7 +8,7 @@ import re
 import urllib.parse
 
 from flask import flash
-from app import current_app, db, log
+from app import current_app, db, log, models
 from app.comp import bonus_spo
 from app.scodoc.sco_exceptions import ScoValueError
 from app.scodoc import sco_utils as scu
@@ -68,7 +68,7 @@ def code_scodoc_to_apo_default(code):
     return CODES_SCODOC_TO_APO.get(code, "DEF")
 
 
-class ScoDocSiteConfig(db.Model):
+class ScoDocSiteConfig(models.ScoDocModel):
     """Config. d'un site
     Nouveau en ScoDoc 9: va regrouper les paramètres qui dans les versions
     antérieures étaient dans scodoc_config.py
diff --git a/app/models/departements.py b/app/models/departements.py
index 64127f6e3ba43b638c97ec49076167e2efbfa47a..18f0181d197772a22093d11cc20e4ae081fa77d7 100644
--- a/app/models/departements.py
+++ b/app/models/departements.py
@@ -4,7 +4,7 @@
 """
 import re
 
-from app import db
+from app import db, models
 from app.models import SHORT_STR_LEN
 from app.models.preferences import ScoPreference
 from app.scodoc.sco_exceptions import ScoValueError
@@ -12,7 +12,7 @@ from app.scodoc.sco_exceptions import ScoValueError
 VALID_DEPT_EXP = re.compile(r"^[\w@\\\-\.]+$")
 
 
-class Departement(db.Model):
+class Departement(models.ScoDocModel):
     """Un département ScoDoc"""
 
     id = db.Column(db.Integer, primary_key=True)
@@ -61,7 +61,7 @@ class Departement(db.Model):
             dept_id = None
         if dept_id is None:
             return cls.query.filter_by(acronym=dept_ident).first_or_404()
-        return cls.query.get_or_404(dept_id)
+        return cls.get_or_404(dept_id)
 
     def to_dict(self, with_dept_name=True, with_dept_preferences=False):
         data = {
diff --git a/app/models/evaluations.py b/app/models/evaluations.py
index 6ec196697881ca08d01d3dcaf055e8885e9e4da9..06db8075d228ac534884905f558833a5f0da4204 100644
--- a/app/models/evaluations.py
+++ b/app/models/evaluations.py
@@ -526,7 +526,7 @@ class Evaluation(models.ScoDocModel):
         )
 
 
-class EvaluationUEPoids(db.Model):
+class EvaluationUEPoids(models.ScoDocModel):
     """Poids des évaluations (BUT)
     association many to many
     """
diff --git a/app/models/events.py b/app/models/events.py
index 2773a7ec0c3c27a4fe191bf0cb10938577fdce36..e0285d9c94c75529bda1555dfea068037a487614 100644
--- a/app/models/events.py
+++ b/app/models/events.py
@@ -62,7 +62,7 @@ class Scolog(ScoDocModel):
         }
 
 
-class ScolarNews(db.Model):
+class ScolarNews(ScoDocModel):
     """Nouvelles pour page d'accueil"""
 
     NEWS_ABS = "ABS"  # saisie absence
diff --git a/app/models/formsemestre.py b/app/models/formsemestre.py
index 0de2eb85459d8723cdd7cccabd6d4954948a2940..863b5d8cecad49d4a1443a6911e5c6c943ea754d 100644
--- a/app/models/formsemestre.py
+++ b/app/models/formsemestre.py
@@ -1575,7 +1575,7 @@ class FormSemestreUECoef(models.ScoDocModel):
     coefficient = db.Column(db.Float, nullable=False)
 
 
-class FormSemestreUEComputationExpr(db.Model):
+class FormSemestreUEComputationExpr(models.ScoDocModel):
     """Formules utilisateurs pour calcul moyenne UE (désactivées en 9.2+)."""
 
     __tablename__ = "notes_formsemestre_ue_computation_expr"
diff --git a/app/models/groups.py b/app/models/groups.py
index 4a07a8753b55bf1daf950ae6baf15ae8cff6cbc1..55cd8ecf9f718d770611adfe1c9e7562e18841ba 100644
--- a/app/models/groups.py
+++ b/app/models/groups.py
@@ -372,11 +372,3 @@ group_membership = db.Table(
     db.Column("group_id", db.Integer, db.ForeignKey("group_descr.id")),
     db.UniqueConstraint("etudid", "group_id"),
 )
-# class GroupMembership(db.Model):
-#     """Association groupe / étudiant"""
-
-#     __tablename__ = "group_membership"
-#     __table_args__ = (db.UniqueConstraint("etudid", "group_id"),)
-#     id = db.Column(db.Integer, primary_key=True)
-#     etudid = db.Column(db.Integer, db.ForeignKey("identite.id", ondelete="CASCADE"))
-#     group_id = db.Column(db.Integer, db.ForeignKey("group_descr.id"))
diff --git a/app/models/modules.py b/app/models/modules.py
index 339b59d5ad3deba80742e845461cb94e582fd459..2df6a8b2c33e7114eb0a585cef40e8a312fb5504 100644
--- a/app/models/modules.py
+++ b/app/models/modules.py
@@ -624,7 +624,7 @@ class Module(models.ScoDocModel):
         return "", http.HTTPStatus.NO_CONTENT
 
 
-class ModuleUECoef(db.Model):
+class ModuleUECoef(models.ScoDocModel):
     """Coefficients des modules vers les UE (APC, BUT)
     En mode APC, ces coefs remplacent le coefficient "PPN" du module.
     """
diff --git a/app/models/notes.py b/app/models/notes.py
index 63e3b23f5fb70d3d25cb88388747c17d357c048f..5f5c93ddb540b6a2a32864a6f31990a6d081b385 100644
--- a/app/models/notes.py
+++ b/app/models/notes.py
@@ -83,7 +83,7 @@ class NotesNotes(models.ScoDocModel):
             } {db.session.get(Evaluation, self.evaluation_id) if self.evaluation_id else "X" }>"""
 
 
-class NotesNotesLog(db.Model):
+class NotesNotesLog(models.ScoDocModel):
     """Historique des modifs sur notes (anciennes entrees de notes_notes)"""
 
     __tablename__ = "notes_notes_log"
diff --git a/app/models/scolar_event.py b/app/models/scolar_event.py
index 4294efb124e0b370c4d52e28b428f50fe706df1c..dbc593b3585712a962397a2d7cc6b1993a3bf9d8 100644
--- a/app/models/scolar_event.py
+++ b/app/models/scolar_event.py
@@ -1,10 +1,11 @@
 """évènements scolaires dans la vie d'un étudiant(inscription, ...)
 """
-from app import db
+
+from app import db, models
 from app.models import SHORT_STR_LEN
 
 
-class ScolarEvent(db.Model):
+class ScolarEvent(models.ScoDocModel):
     """Evenement dans le parcours scolaire d'un étudiant"""
 
     __tablename__ = "scolar_events"
diff --git a/app/models/ues.py b/app/models/ues.py
index dce90b4e6c77d774317d182b8edfa675150686d8..4430c0a1fbb45fe21cb8ed6e893954a479e9770c 100644
--- a/app/models/ues.py
+++ b/app/models/ues.py
@@ -573,7 +573,7 @@ class UniteEns(models.ScoDocModel):
         return self.set_parcours(self.parcours + [parcour])
 
 
-class UEParcours(db.Model):
+class UEParcours(models.ScoDocModel):
     """Association ue <-> parcours, indiquant les ECTS"""
 
     __tablename__ = "ue_parcours"
@@ -593,7 +593,7 @@ class UEParcours(db.Model):
         return f"<UEParcours( ue_id={self.ue_id}, parcours_id={self.parcours_id}, ects={self.ects})>"
 
 
-class DispenseUE(db.Model):
+class DispenseUE(models.ScoDocModel):
     """Dispense d'UE
     Utilisé en APC (BUT) pour indiquer
     - les étudiants redoublants avec une UE capitalisée qu'ils ne refont pas.
diff --git a/app/models/validations.py b/app/models/validations.py
index 150bb94a296a3c68f57629c73c421fdf21fa3c6b..fee681ee2cbefa733e86b59cf88b163e9aaf9f20 100644
--- a/app/models/validations.py
+++ b/app/models/validations.py
@@ -4,8 +4,7 @@
 """
 from flask_sqlalchemy.query import Query
 
-from app import db
-from app import log
+from app import db, log, models
 from app.models import SHORT_STR_LEN
 from app.models import CODE_STR_LEN
 from app.models.events import Scolog
@@ -16,7 +15,7 @@ from app.scodoc import sco_utils as scu
 from app.scodoc.codes_cursus import CODES_UE_VALIDES
 
 
-class ScolarFormSemestreValidation(db.Model):
+class ScolarFormSemestreValidation(models.ScoDocModel):
     """Décisions de jury (sur semestre ou UEs)"""
 
     __tablename__ = "scolar_formsemestre_validation"
@@ -158,7 +157,7 @@ class ScolarFormSemestreValidation(db.Model):
         )
 
 
-class ScolarAutorisationInscription(db.Model):
+class ScolarAutorisationInscription(models.ScoDocModel):
     """Autorisation d'inscription dans un semestre"""
 
     __tablename__ = "scolar_autorisation_inscription"
diff --git a/app/scodoc/sco_apogee_csv.py b/app/scodoc/sco_apogee_csv.py
index f2dba69c97bb2f61db34f912de5d65feef0bc2d4..f054a296c95a5461e9113f46eddadf4efd364eb0 100644
--- a/app/scodoc/sco_apogee_csv.py
+++ b/app/scodoc/sco_apogee_csv.py
@@ -282,7 +282,7 @@ class ApoEtud(dict):
         ):
             res = self.autre_res
         else:
-            formsemestre = FormSemestre.query.get_or_404(sem["formsemestre_id"])
+            formsemestre = FormSemestre.get_or_404(sem["formsemestre_id"])
             res: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
 
         if etudid not in res.identdict:
@@ -761,7 +761,7 @@ class ApoData:
         if not self.sems_etape:
             raise ScoValueError("aucun semestre trouvé !")
         self.formsemestres_etape = [
-            FormSemestre.query.get_or_404(s["formsemestre_id"]) for s in self.sems_etape
+            FormSemestre.get_or_404(s["formsemestre_id"]) for s in self.sems_etape
         ]
         apcs = {
             formsemestre.formation.is_apc() for formsemestre in self.formsemestres_etape
@@ -903,9 +903,7 @@ class ApoData:
         """
         codes_by_sem = {}
         for sem in self.sems_etape:
-            formsemestre: FormSemestre = FormSemestre.query.get_or_404(
-                sem["formsemestre_id"]
-            )
+            formsemestre: FormSemestre = FormSemestre.get_or_404(sem["formsemestre_id"])
             # L'ensemble des codes apo associés aux éléments:
             codes_semestre = formsemestre.get_codes_apogee()
             codes_modules = set().union(
diff --git a/app/scodoc/sco_archives_formsemestre.py b/app/scodoc/sco_archives_formsemestre.py
index 31548bcd6d4b7144112070f62fede310a71dc463..892e9438a5f1fc5b354dbf14770c426d27d2f029 100644
--- a/app/scodoc/sco_archives_formsemestre.py
+++ b/app/scodoc/sco_archives_formsemestre.py
@@ -416,7 +416,7 @@ def formsemestre_get_archived_file(formsemestre_id, archive_name, filename):
 
 def formsemestre_delete_archive(formsemestre_id, archive_name, dialog_confirmed=False):
     """Delete an archive"""
-    formsemestre: FormSemestre = FormSemestre.query.get_or_404(formsemestre_id)
+    formsemestre: FormSemestre = FormSemestre.get_or_404(formsemestre_id)
     if not formsemestre.can_edit_pv():
         raise ScoPermissionDenied(
             dest_url=url_for(
diff --git a/app/scodoc/sco_bulletins.py b/app/scodoc/sco_bulletins.py
index e2b4f3c81a9ae3f52bf1488c2511d4aad522b5d1..e59249926cf6d3827a5c7cbf26734bf31170949c 100644
--- a/app/scodoc/sco_bulletins.py
+++ b/app/scodoc/sco_bulletins.py
@@ -159,7 +159,7 @@ def formsemestre_bulletinetud_dict(formsemestre_id, etudid, version="long"):
 
     # Formation et parcours
     if I["sem"]["formation_id"]:
-        formation_dict = Formation.query.get_or_404(I["sem"]["formation_id"]).to_dict()
+        formation_dict = Formation.get_or_404(I["sem"]["formation_id"]).to_dict()
     else:  # what's the fuck ?
         formation_dict = {
             "acronyme": "?",
diff --git a/app/scodoc/sco_bulletins_pdf.py b/app/scodoc/sco_bulletins_pdf.py
index 678c7d96d31bf77d9f211fc3b524e28186563f2f..8b11a07014d2704f82987ac73ae6d5620e6651e6 100644
--- a/app/scodoc/sco_bulletins_pdf.py
+++ b/app/scodoc/sco_bulletins_pdf.py
@@ -225,7 +225,7 @@ def get_formsemestre_bulletins_pdf(
     from app.but import bulletin_but_court
     from app.scodoc import sco_bulletins
 
-    formsemestre: FormSemestre = FormSemestre.query.get_or_404(formsemestre_id)
+    formsemestre: FormSemestre = FormSemestre.get_or_404(formsemestre_id)
     versions = (
         scu.BULLETINS_VERSIONS_BUT
         if formsemestre.formation.is_apc()
diff --git a/app/scodoc/sco_cursus_dut.py b/app/scodoc/sco_cursus_dut.py
index e4f7746b38fdc625ecb7b7f31bf5a2db667bf88c..d4605454eab51f78be02d175f0842206ec1f22b8 100644
--- a/app/scodoc/sco_cursus_dut.py
+++ b/app/scodoc/sco_cursus_dut.py
@@ -313,7 +313,7 @@ class SituationEtudCursusClassic(SituationEtudCursus):
         sont validés. En sortie, sem_idx_set contient ceux qui n'ont pas été validés."""
         for sem in self.get_semestres():
             if sem["formation_code"] == self.formation.formation_code:
-                formsemestre = FormSemestre.query.get_or_404(sem["formsemestre_id"])
+                formsemestre = FormSemestre.get_or_404(sem["formsemestre_id"])
                 nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
                 decision = nt.get_etud_decision_sem(self.etudid)
                 if decision and code_semestre_validant(decision["code"]):
@@ -408,7 +408,7 @@ class SituationEtudCursusClassic(SituationEtudCursus):
             if not sem:
                 code = ""  # non inscrit à ce semestre
             else:
-                formsemestre = FormSemestre.query.get_or_404(sem["formsemestre_id"])
+                formsemestre = FormSemestre.get_or_404(sem["formsemestre_id"])
                 nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
                 decision = nt.get_etud_decision_sem(self.etudid)
                 if decision:
@@ -949,7 +949,7 @@ def do_formsemestre_validate_ue(
 ):
     """Ajoute ou change validation UE"""
     if semestre_id is None:
-        ue = UniteEns.query.get_or_404(ue_id)
+        ue = UniteEns.get_or_404(ue_id)
         semestre_id = ue.semestre_idx
     args = {
         "formsemestre_id": formsemestre_id,
diff --git a/app/scodoc/sco_etape_apogee_view.py b/app/scodoc/sco_etape_apogee_view.py
index 1dda5aaab79d80fd8d2200846ad2a8c935d46e43..0e7913fd2f0ebcae4a80c5516d2a647c4aad36e8 100644
--- a/app/scodoc/sco_etape_apogee_view.py
+++ b/app/scodoc/sco_etape_apogee_view.py
@@ -347,7 +347,7 @@ def apo_semset_maq_status(
     if missing:
         formation_ids = {sem["formation_id"] for sem in semset.sems}
         formations = [
-            Formation.query.get_or_404(formation_id) for formation_id in formation_ids
+            Formation.get_or_404(formation_id) for formation_id in formation_ids
         ]
         H.append(
             f"""<div class="apo_csv_status_missing_elems">
diff --git a/app/scodoc/sco_export_results.py b/app/scodoc/sco_export_results.py
index b372c72c0d37382189acf25616eb4af76c5a8eaf..aafeabe72c5da8fd428f971505b9578f064c15af 100644
--- a/app/scodoc/sco_export_results.py
+++ b/app/scodoc/sco_export_results.py
@@ -64,7 +64,7 @@ def _build_results_table(start_date=None, end_date=None, types_parcours=()):
     semlist = [dpv["formsemestre"] for dpv in dpv_by_sem.values() if dpv]
     semlist_parcours = []
     for sem in semlist:
-        sem["formation"] = Formation.query.get_or_404(sem["formation_id"]).to_dict()
+        sem["formation"] = Formation.get_or_404(sem["formation_id"]).to_dict()
         sem["parcours"] = codes_cursus.get_cursus_from_code(
             sem["formation"]["type_parcours"]
         )
diff --git a/app/scodoc/sco_formsemestre.py b/app/scodoc/sco_formsemestre.py
index 31dffb7365c635d6e2d02131b5039a9351ba17f2..8ca5a42fab6605e6080c8d0848daec3fe0a5c857 100644
--- a/app/scodoc/sco_formsemestre.py
+++ b/app/scodoc/sco_formsemestre.py
@@ -148,7 +148,7 @@ def _formsemestre_enrich(sem):
     # imports ici pour eviter refs circulaires
     from app.scodoc import sco_formsemestre_edit
 
-    formation: Formation = Formation.query.get_or_404(sem["formation_id"])
+    formation: Formation = Formation.get_or_404(sem["formation_id"])
     parcours = codes_cursus.get_cursus_from_code(formation.type_parcours)
     # 'S1', 'S2', ... ou '' pour les monosemestres
     if sem["semestre_id"] != NO_SEMESTRE_ID:
diff --git a/app/scodoc/sco_formsemestre_edit.py b/app/scodoc/sco_formsemestre_edit.py
index 02227ab6bf2b19d622ae79373266c9be4754d940..74f1d748155937f4e1230406c2fbc69227567dd5 100644
--- a/app/scodoc/sco_formsemestre_edit.py
+++ b/app/scodoc/sco_formsemestre_edit.py
@@ -181,7 +181,7 @@ def do_formsemestre_createwithmodules(edit=False, formsemestre: FormSemestre = N
         formation = formsemestre.formation
     else:
         formation_id = int(vals["formation_id"])
-        formation = Formation.query.get_or_404(formation_id)
+        formation = Formation.get_or_404(formation_id)
 
     is_apc = formation.is_apc()
     if not edit:
@@ -1076,10 +1076,10 @@ def _formsemestre_check_module_list(module_ids, semestre_idx):
     """
     # vérification de la cohérence / modules / semestre
     mod_sems_idx = {
-        Module.query.get_or_404(module_id).ue.semestre_idx for module_id in module_ids
+        Module.get_or_404(module_id).ue.semestre_idx for module_id in module_ids
     }
     if mod_sems_idx and mod_sems_idx != {semestre_idx}:
-        modules = [Module.query.get_or_404(module_id) for module_id in module_ids]
+        modules = [Module.get_or_404(module_id) for module_id in module_ids]
         log(
             f"""_formsemestre_check_module_list:
         {chr(10).join( str(module) + " " + str(module.ue) for module in modules )}
@@ -1097,7 +1097,7 @@ def _formsemestre_check_module_list(module_ids, semestre_idx):
 
 def _formsemestre_check_ue_bonus_unicity(module_ids):
     """Vérifie qu'il n'y a qu'une seule UE bonus associée aux modules choisis"""
-    ues = [Module.query.get_or_404(module_id).ue for module_id in module_ids]
+    ues = [Module.get_or_404(module_id).ue for module_id in module_ids]
     ues_bonus = {ue.id for ue in ues if ue.type == codes_cursus.UE_SPORT}
     if len(ues_bonus) > 1:
         raise ScoValueError(
@@ -1294,9 +1294,7 @@ def do_formsemestre_clone(
     New dates, responsable_id
     """
     log(f"do_formsemestre_clone: {orig_formsemestre_id}")
-    formsemestre_orig: FormSemestre = FormSemestre.query.get_or_404(
-        orig_formsemestre_id
-    )
+    formsemestre_orig: FormSemestre = FormSemestre.get_or_404(orig_formsemestre_id)
     # 1- create sem
     args = formsemestre_orig.to_dict()
     del args["formsemestre_id"]
diff --git a/app/scodoc/sco_formsemestre_exterieurs.py b/app/scodoc/sco_formsemestre_exterieurs.py
index da71ee64b0a61a6205cc472b200b604e74bff24a..1e4f7a976b2633d74a6848fa8799e90cc5877e42 100644
--- a/app/scodoc/sco_formsemestre_exterieurs.py
+++ b/app/scodoc/sco_formsemestre_exterieurs.py
@@ -61,7 +61,7 @@ def formsemestre_ext_create(etud: Identite | None, sem_params: dict) -> FormSeme
     sem_params: dict nécessaire à la création du formsemestre
     """
     # Check args
-    _ = Formation.query.get_or_404(sem_params["formation_id"])
+    _ = Formation.get_or_404(sem_params["formation_id"])
 
     # Create formsemestre
     sem_params["modalite"] = "EXT"
@@ -230,7 +230,7 @@ def formsemestre_ext_edit_ue_validations(formsemestre_id, etudid):
     La moyenne générale indicative du semestre est calculée et affichée,
     mais pas enregistrée.
     """
-    formsemestre: FormSemestre = FormSemestre.query.get_or_404(formsemestre_id)
+    formsemestre: FormSemestre = FormSemestre.get_or_404(formsemestre_id)
     etud = Identite.get_etud(etudid)
     ues = formsemestre.formation.ues.filter(UniteEns.type != UE_SPORT).order_by(
         UniteEns.semestre_idx, UniteEns.numero
diff --git a/app/scodoc/sco_formsemestre_inscriptions.py b/app/scodoc/sco_formsemestre_inscriptions.py
index 33ffb4bdb7a95b2fcccafa78febb4b0189c19c71..9e9324255eb24f413004a5c845a1af2234c2eeb7 100644
--- a/app/scodoc/sco_formsemestre_inscriptions.py
+++ b/app/scodoc/sco_formsemestre_inscriptions.py
@@ -248,9 +248,7 @@ def do_formsemestre_inscription_with_modules(
         group_ids = [group_ids]
     # Check that all groups exist before creating the inscription
     groups = [
-        GroupDescr.query.get_or_404(group_id)
-        for group_id in group_ids
-        if group_id != ""
+        GroupDescr.get_or_404(group_id) for group_id in group_ids if group_id != ""
     ]
     formsemestre = FormSemestre.get_formsemestre(formsemestre_id, dept_id=dept_id)
     # Inscription au semestre
@@ -377,7 +375,7 @@ def formsemestre_inscription_with_modules(
     )
     if multiple_ok:
         multiple_ok = int(multiple_ok)
-    formsemestre: FormSemestre = FormSemestre.query.get_or_404(formsemestre_id)
+    formsemestre: FormSemestre = FormSemestre.get_or_404(formsemestre_id)
     etud = Identite.get_etud(etudid)
     if etud.dept_id != formsemestre.dept_id:
         raise ScoValueError("l'étudiant n'est pas dans ce département")
diff --git a/app/scodoc/sco_formsemestre_status.py b/app/scodoc/sco_formsemestre_status.py
index 1d971fe8e6d9afe740218b7d0db68a9017fcbd4e..e4a404d1821ac8552c8ea49121b5b7086d9a20a4 100755
--- a/app/scodoc/sco_formsemestre_status.py
+++ b/app/scodoc/sco_formsemestre_status.py
@@ -1079,7 +1079,7 @@ def formsemestre_status(formsemestre_id=None, check_parcours=True):
         raise ScoInvalidIdType(
             "formsemestre_bulletinetud: formsemestre_id must be an integer !"
         )
-    formsemestre: FormSemestre = FormSemestre.query.get_or_404(formsemestre_id)
+    formsemestre: FormSemestre = FormSemestre.get_or_404(formsemestre_id)
     # S'assure que les groupes de parcours sont à jour:
     if int(check_parcours):
         formsemestre.setup_parcours_groups()
diff --git a/app/scodoc/sco_formsemestre_validation.py b/app/scodoc/sco_formsemestre_validation.py
index 675abed9eb75b87bdb930e74f75a510c65f332ff..093fc168ca43a42d2435219589728937ad8dac59 100644
--- a/app/scodoc/sco_formsemestre_validation.py
+++ b/app/scodoc/sco_formsemestre_validation.py
@@ -1383,7 +1383,7 @@ def do_formsemestre_validate_previous_ue(
     cette UE (utile seulement pour les semestres extérieurs).
     """
     nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
-    ue: UniteEns = UniteEns.query.get_or_404(ue_id)
+    ue: UniteEns = UniteEns.get_or_404(ue_id)
 
     cnx = ndb.GetDBConnexion()
     if ue_coefficient is not None:
diff --git a/app/scodoc/sco_groups.py b/app/scodoc/sco_groups.py
index ae9d7e1ca3c334d1fad87911902c393bfa1b6d3d..3c83e9d0c3396fbfb5d2261598ac13b1b7122d59 100644
--- a/app/scodoc/sco_groups.py
+++ b/app/scodoc/sco_groups.py
@@ -663,7 +663,7 @@ def change_etud_group_in_partition(etudid: int, group: GroupDescr) -> bool:
     (et le désinscrit d'autres groupes de cette partition)
     Return True si changement, False s'il était déjà dans ce groupe.
     """
-    etud: Identite = Identite.query.get_or_404(etudid)
+    etud: Identite = Identite.get_or_404(etudid)
     if not group.partition.set_etud_group(etud, group):
         return  # pas de changement
 
@@ -742,7 +742,7 @@ groupsToDelete={groupsToDelete}
         except ValueError:
             log(f"setGroups: ignoring invalid group_id={group_id}")
             continue
-        group: GroupDescr = GroupDescr.query.get_or_404(group_id)
+        group: GroupDescr = GroupDescr.get_or_404(group_id)
         # Anciens membres du groupe:
         old_members_set = {etud.id for etud in group.etuds}
         # Place dans ce groupe les etudiants indiqués:
@@ -807,7 +807,7 @@ def create_group(partition_id, group_name="", default=False) -> GroupDescr:
     If default, create default partition (with no name)
     Obsolete: utiliser Partition.create_group
     """
-    partition = Partition.query.get_or_404(partition_id)
+    partition = Partition.get_or_404(partition_id)
     if not partition.formsemestre.can_change_groups():
         raise AccessDenied("Vous n'avez pas le droit d'effectuer cette opération !")
     #
@@ -840,7 +840,7 @@ def delete_group(group_id, partition_id=None):
     est bien dans cette partition.
     S'il s'agit d'un groupe de parcours, affecte l'inscription des étudiants aux parcours.
     """
-    group = GroupDescr.query.get_or_404(group_id)
+    group = GroupDescr.get_or_404(group_id)
     if partition_id:
         if partition_id != group.partition_id:
             raise ValueError("inconsistent partition/group")
@@ -1096,7 +1096,7 @@ def partition_set_attr(partition_id, attr, value):
     if attr not in {"bul_show_rank", "show_in_lists"}:
         raise ValueError(f"invalid partition attribute: {attr}")
 
-    partition = Partition.query.get_or_404(partition_id)
+    partition = Partition.get_or_404(partition_id)
     if not partition.formsemestre.can_change_groups():
         raise AccessDenied("Vous n'avez pas le droit d'effectuer cette opération !")
 
diff --git a/app/scodoc/sco_groups_edit.py b/app/scodoc/sco_groups_edit.py
index 9a6290b8cbfec2185d76b3f83869a314698b0c4e..0db4d638f181f85aa55b38a3b61431e86889b9e5 100644
--- a/app/scodoc/sco_groups_edit.py
+++ b/app/scodoc/sco_groups_edit.py
@@ -42,7 +42,7 @@ def affect_groups(partition_id):
     Permet aussi la creation et la suppression de groupes.
     """
     # réécrit pour 9.0.47 avec un template
-    partition = Partition.query.get_or_404(partition_id)
+    partition = Partition.get_or_404(partition_id)
     formsemestre = partition.formsemestre
     if not formsemestre.can_change_groups():
         raise AccessDenied("vous n'avez pas la permission de modifier les groupes")
@@ -63,7 +63,7 @@ def affect_groups(partition_id):
 
 def group_rename(group_id):
     """Form to rename a group"""
-    group: GroupDescr = GroupDescr.query.get_or_404(group_id)
+    group: GroupDescr = GroupDescr.get_or_404(group_id)
     formsemestre_id = group.partition.formsemestre_id
     formsemestre = FormSemestre.get_formsemestre(formsemestre_id)
     if not formsemestre.can_change_groups():
diff --git a/app/scodoc/sco_groups_view.py b/app/scodoc/sco_groups_view.py
index f38c4cf90b0f2f53a453aaefff1eb97c82328ccd..adc145b5809d608e5fefe648aa751750e9658c32 100644
--- a/app/scodoc/sco_groups_view.py
+++ b/app/scodoc/sco_groups_view.py
@@ -370,7 +370,7 @@ class DisplayedGroupsInfos:
 
         if not group_ids:  # appel sans groupe (eg page accueil)
             if not formsemestre_id:
-                raise ValueError("missing parameter formsemestre_id or group_ids")
+                raise ScoValueError("missing parameter formsemestre_id or group_ids")
             if empty_list_select_all:
                 if select_all_when_unspecified:
                     group_ids = [
diff --git a/app/scodoc/sco_moduleimpl_inscriptions.py b/app/scodoc/sco_moduleimpl_inscriptions.py
index 8ea83d7b0acd0f9445a9a87b99e736c6acff78fb..50540986132ca6ad152d5c10dd4250ee761f98cf 100644
--- a/app/scodoc/sco_moduleimpl_inscriptions.py
+++ b/app/scodoc/sco_moduleimpl_inscriptions.py
@@ -399,7 +399,7 @@ def moduleimpl_inscriptions_stats(formsemestre_id):
         H.append(
             '<h3>Étudiants avec UEs capitalisées (ADM):</h3><ul class="ue_inscr_list">'
         )
-        ues = [UniteEns.query.get_or_404(ue_id) for ue_id in ues_cap_info.keys()]
+        ues = [UniteEns.get_or_404(ue_id) for ue_id in ues_cap_info.keys()]
         ues.sort(key=lambda u: u.numero)
         for ue in ues:
             H.append(
diff --git a/app/scodoc/sco_photos.py b/app/scodoc/sco_photos.py
index 0e69214be88a10ca1df7b97a4a43e294501ec81c..f034da8767cf7332c88efef9866a4c7d7d68f294 100755
--- a/app/scodoc/sco_photos.py
+++ b/app/scodoc/sco_photos.py
@@ -369,7 +369,7 @@ def copy_portal_photo_to_fs(etudid: int):
     """Copy the photo from portal (distant website) to local fs.
     Returns rel. path or None if copy failed, with a diagnostic message
     """
-    etud: Identite = Identite.query.get_or_404(etudid)
+    etud: Identite = Identite.get_or_404(etudid)
     url = photo_portal_url(etud.code_nip)
     if not url:
         return None, f"""{etud.nomprenom}: pas de code NIP"""
diff --git a/app/scodoc/sco_poursuite_dut.py b/app/scodoc/sco_poursuite_dut.py
index 42fcd31012994cb403f9f2444ab1b0d9aae11fc9..add16a0e949ffc394972f969c1c259f46c61b858 100644
--- a/app/scodoc/sco_poursuite_dut.py
+++ b/app/scodoc/sco_poursuite_dut.py
@@ -60,7 +60,7 @@ def etud_get_poursuite_info(sem: dict, etud: dict) -> dict:
         for s in etud["sems"]:
             if s["semestre_id"] == sem_id:
                 etudid = etud["etudid"]
-                formsemestre = FormSemestre.query.get_or_404(s["formsemestre_id"])
+                formsemestre = FormSemestre.get_or_404(s["formsemestre_id"])
                 nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
                 dec = nt.get_etud_decision_sem(etudid)
                 # Moyennes et rangs des UE
diff --git a/app/scodoc/sco_pv_dict.py b/app/scodoc/sco_pv_dict.py
index b62dd11a55f5b1907bd628c59c2f9a36fb62a9c1..07df64a55b8f2422517216e8497037b8824ef5db 100644
--- a/app/scodoc/sco_pv_dict.py
+++ b/app/scodoc/sco_pv_dict.py
@@ -91,7 +91,7 @@ def dict_pvjury(
         'decisions_dict' : { etudid : decision (comme ci-dessus) },
     }
     """
-    formsemestre: FormSemestre = FormSemestre.query.get_or_404(formsemestre_id)
+    formsemestre: FormSemestre = FormSemestre.get_or_404(formsemestre_id)
     nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
     if etudids is None:
         etudids = nt.get_etudids()
@@ -231,7 +231,7 @@ def dict_pvjury(
         "is_apc": nt.is_apc,
         "has_prev": has_prev,
         "semestre_non_terminal": semestre_non_terminal,
-        "formation": Formation.query.get_or_404(sem["formation_id"]).to_dict(),
+        "formation": Formation.get_or_404(sem["formation_id"]).to_dict(),
         "decisions": decisions,
         "decisions_dict": D,
     }
diff --git a/app/scodoc/sco_pv_forms.py b/app/scodoc/sco_pv_forms.py
index e8aa3f06899eae90377c1c7296c1032d457ade37..d5930bbde1c48c6d896eca18f4f3dac385c2abe1 100644
--- a/app/scodoc/sco_pv_forms.py
+++ b/app/scodoc/sco_pv_forms.py
@@ -577,7 +577,7 @@ def descrform_pvjury(formsemestre: FormSemestre):
 
 def formsemestre_lettres_individuelles(formsemestre_id):
     "Lettres avis jury en PDF"
-    formsemestre: FormSemestre = FormSemestre.query.get_or_404(formsemestre_id)
+    formsemestre: FormSemestre = FormSemestre.get_or_404(formsemestre_id)
     if request.method == "POST":
         group_ids = request.form.getlist("group_ids")
     else:
diff --git a/app/scodoc/sco_report.py b/app/scodoc/sco_report.py
index f234786fff1d9b8144c2ff515cd1992893a25c9e..aba7d6d11505010a82284d9a6d54080447d67557 100644
--- a/app/scodoc/sco_report.py
+++ b/app/scodoc/sco_report.py
@@ -533,7 +533,7 @@ def table_suivi_cohorte(
         s["members"] = orig_set.intersection(inset)
         nb_dipl = 0  # combien de diplomes dans ce semestre ?
         if s["semestre_id"] == nt.parcours.NB_SEM:
-            s_formsemestre = FormSemestre.query.get_or_404(s["formsemestre_id"])
+            s_formsemestre = FormSemestre.get_or_404(s["formsemestre_id"])
             nt: NotesTableCompat = res_sem.load_formsemestre_results(s_formsemestre)
             for etudid in s["members"]:
                 dec = nt.get_etud_decision_sem(etudid)
@@ -1114,7 +1114,7 @@ def get_code_cursus_etud(
 
     if formsemestres is None:
         formsemestres = [
-            FormSemestre.query.get_or_404(s["formsemestre_id"]) for s in (sems or [])
+            FormSemestre.get_or_404(s["formsemestre_id"]) for s in (sems or [])
         ]
 
     # élimine les semestres spéciaux hors cursus (LP en 1 sem., ...)
@@ -1448,7 +1448,7 @@ def graph_cursus(
         nxt = {}
         etudid = etud["etudid"]
         for s in etud["sems"]:  # du plus recent au plus ancien
-            s_formsemestre = FormSemestre.query.get_or_404(s["formsemestre_id"])
+            s_formsemestre = FormSemestre.get_or_404(s["formsemestre_id"])
             nt: NotesTableCompat = res_sem.load_formsemestre_results(s_formsemestre)
             dec = nt.get_etud_decision_sem(etudid)
             if nxt:
diff --git a/app/scodoc/sco_report_but.py b/app/scodoc/sco_report_but.py
index 8a685a118877d21812a02347b3f161f4b92269c6..b57dc2c416c792738e678a269cd9f5a98af3976e 100644
--- a/app/scodoc/sco_report_but.py
+++ b/app/scodoc/sco_report_but.py
@@ -66,7 +66,7 @@ INDICATEUR_NAMES = {
 
 def formsemestre_but_indicateurs(formsemestre_id: int, fmt="html"):
     """Page avec tableau indicateurs enquête ADIUT BUT 2022"""
-    formsemestre: FormSemestre = FormSemestre.query.get_or_404(formsemestre_id)
+    formsemestre: FormSemestre = FormSemestre.get_or_404(formsemestre_id)
 
     indicateurs_by_bac = but_indicateurs_by_bac(formsemestre)
     # finalement on fait une table avec en ligne
diff --git a/app/scodoc/sco_saisie_notes.py b/app/scodoc/sco_saisie_notes.py
index 10dfed91e26184b78333d9c74e58bdac21822cd2..cb13fccd03290819e1237e73a920b2bf3237c2f9 100644
--- a/app/scodoc/sco_saisie_notes.py
+++ b/app/scodoc/sco_saisie_notes.py
@@ -326,7 +326,7 @@ def do_evaluation_set_missing(
 
 def evaluation_suppress_alln(evaluation_id, dialog_confirmed=False):
     "suppress all notes in this eval"
-    evaluation = Evaluation.query.get_or_404(evaluation_id)
+    evaluation = Evaluation.get_or_404(evaluation_id)
 
     if evaluation.moduleimpl.can_edit_notes(current_user, allow_ens=False):
         # On a le droit de modifier toutes les notes
diff --git a/app/scodoc/sco_semset.py b/app/scodoc/sco_semset.py
index a1d6cb26eb8a4af87df9511d17289217f57366ad..18d83d07a2a047c356deda95c32f8a11b9212c07 100644
--- a/app/scodoc/sco_semset.py
+++ b/app/scodoc/sco_semset.py
@@ -157,7 +157,7 @@ class SemSet(dict):
     def add(self, formsemestre_id):
         "Ajoute ce semestre à l'ensemble"
         # check for valid formsemestre_id
-        formsemestre: FormSemestre = FormSemestre.query.get_or_404(formsemestre_id)
+        formsemestre: FormSemestre = FormSemestre.get_or_404(formsemestre_id)
         # check
         if formsemestre_id in self.formsemestre_ids:
             return  # already there
@@ -265,7 +265,7 @@ class SemSet(dict):
         self["jury_nb_missing"] = 0
         is_apc = None
         for sem in self.sems:
-            formsemestre = FormSemestre.query.get_or_404(sem["formsemestre_id"])
+            formsemestre = FormSemestre.get_or_404(sem["formsemestre_id"])
             nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
             if is_apc is not None and is_apc != nt.is_apc:
                 raise ScoValueError(
diff --git a/app/scodoc/sco_synchro_etuds.py b/app/scodoc/sco_synchro_etuds.py
index 41841e943b68bb23c513744043d1916658333571..e8879d3ad70d54b04677cbfec0714f56abb0ea2d 100644
--- a/app/scodoc/sco_synchro_etuds.py
+++ b/app/scodoc/sco_synchro_etuds.py
@@ -848,7 +848,7 @@ def formsemestre_import_etud_admission(
 
     for i in ins:
         etudid = i["etudid"]
-        etud: Identite = Identite.query.get_or_404(etudid)
+        etud: Identite = Identite.get_or_404(etudid)
         code_nip = etud.code_nip
         if not code_nip:
             etuds_no_nip.append(etud)
diff --git a/app/scodoc/sco_undo_notes.py b/app/scodoc/sco_undo_notes.py
index b42dd6b7526d4e4449fa65e1425210b097aba31b..9d9102b2a21cb547b5de6347ff7c3653f67ff67e 100644
--- a/app/scodoc/sco_undo_notes.py
+++ b/app/scodoc/sco_undo_notes.py
@@ -182,7 +182,7 @@ def formsemestre_list_saisies_notes(formsemestre_id, fmt="html"):
     """Table listant toutes les opérations de saisies de notes, dans toutes
     les évaluations du semestre.
     """
-    formsemestre: FormSemestre = FormSemestre.query.get_or_404(formsemestre_id)
+    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
diff --git a/app/tables/liste_assiduites.py b/app/tables/liste_assiduites.py
index 1051a0fb012b9d9e4164422dd4c9b70f68f135a1..10984cb13ba18003acbc0b54f124e903e243d26a 100644
--- a/app/tables/liste_assiduites.py
+++ b/app/tables/liste_assiduites.py
@@ -5,14 +5,14 @@ Gestion des listes d'assiduités et justificatifs
 
 from datetime import datetime
 
-from flask import url_for, request
+from flask import url_for
 from flask_login import current_user
 from flask_sqlalchemy.query import Query
 from sqlalchemy import desc, literal, union, asc
 
 from app import db, g
 from app.auth.models import User
-from app.models import Assiduite, Identite, Justificatif, Module
+from app.models import Assiduite, Identite, Justificatif, Module, ScoDocModel
 import app.scodoc.sco_utils as scu
 
 from app.scodoc.sco_utils import (
@@ -715,7 +715,7 @@ class AssiFiltre:
         if date_fin is not None:
             self.filtres["date_fin"]: tuple[int, datetime] = date_fin
 
-    def filtrage(self, query: Query, obj_class: db.Model) -> Query:
+    def filtrage(self, query: Query, obj_class: ScoDocModel) -> Query:
         """
         filtrage Filtre la query passée en paramètre et retourne l'objet filtré
 
diff --git a/app/views/assiduites.py b/app/views/assiduites.py
index e29af120a56274db9beffc99a3be7756271aeb9b..8d89626fb1f7a7859549ac2be77b9321a3beab42 100644
--- a/app/views/assiduites.py
+++ b/app/views/assiduites.py
@@ -1077,7 +1077,7 @@ def signal_assiduites_group():
     # --- Filtrage par formsemestre ---
     formsemestre_id = groups_infos.formsemestre_id
 
-    formsemestre: FormSemestre = FormSemestre.query.get_or_404(formsemestre_id)
+    formsemestre: FormSemestre = FormSemestre.get_or_404(formsemestre_id)
     if formsemestre.dept_id != g.scodoc_dept_id:
         abort(404, "groupes inexistants dans ce département")
 
@@ -1243,7 +1243,7 @@ def etat_abs_date():
         raise ScoValueError("date_fin invalide") from exc
 
     # Les groupes:
-    groups = [GroupDescr.query.get_or_404(group_id) for group_id in group_ids]
+    groups = [GroupDescr.get_or_404(group_id) for group_id in group_ids]
     # Les étudiants de tous les groupes sélectionnés, flat list
     etuds = [
         etud for gr_etuds in [group.etuds for group in groups] for etud in gr_etuds
@@ -1534,11 +1534,11 @@ def recup_assiduites_plage():
     name: str = ""
 
     if formsemestre_id is not None and formsemestre_id != "":
-        formsemestre: FormSemestre = FormSemestre.query.get_or_404(formsemestre_id)
+        formsemestre: FormSemestre = FormSemestre.get_or_404(formsemestre_id)
         etuds = formsemestre.etuds
         name = formsemestre.session_id()
     else:
-        dept: Departement = Departement.query.get_or_404(g.scodoc_dept_id)
+        dept: Departement = Departement.get_or_404(g.scodoc_dept_id)
         etuds = dept.etudiants
         name = dept.acronym
 
@@ -1603,11 +1603,11 @@ def tableau_assiduite_actions():
     objet_name = ""
     e = ""
     if obj_type == "assiduite":
-        objet: Assiduite = Assiduite.query.get_or_404(obj_id)
+        objet: Assiduite = Assiduite.get_or_404(obj_id)
         objet_name = scu.EtatAssiduite(objet.etat).version_lisible()
         e = scu.EtatAssiduite(objet.etat).e()
     else:
-        objet: Justificatif = Justificatif.query.get_or_404(obj_id)
+        objet: Justificatif = Justificatif.get_or_404(obj_id)
         objet_name = "Justificatif"
 
     # Suppression : attention, POST ou GET !
@@ -1738,7 +1738,7 @@ def signale_evaluation_abs(etudid: int = None, evaluation_id: int = None):
     sinon l'utilisateur sera redirigé vers la page de saisie des absences de l'étudiant
     """
     etud = Identite.get_etud(etudid)
-    evaluation: Evaluation = Evaluation.query.get_or_404(evaluation_id)
+    evaluation: Evaluation = Evaluation.get_or_404(evaluation_id)
 
     delta: datetime.timedelta = evaluation.date_fin - evaluation.date_debut
     # Si l'évaluation dure plus qu'un jour alors on redirige vers la page de saisie etudiant
diff --git a/app/views/but_formation.py b/app/views/but_formation.py
index e4bd83670ef3d28a5d653dfb80850f3f6865ce96..62f67b087ce987b99a9ac3955af4bf1195b8e118 100644
--- a/app/views/but_formation.py
+++ b/app/views/but_formation.py
@@ -105,8 +105,8 @@ def validation_rcues(
     """Visualisation des résultats UEs et RCUEs d'un étudiant
     et saisie des validation de RCUE antérieures.
     """
-    etud: Identite = Identite.query.get_or_404(etudid)
-    formsemestre: FormSemestre = FormSemestre.query.get_or_404(formsemestre_id)
+    etud: Identite = Identite.get_or_404(etudid)
+    formsemestre: FormSemestre = FormSemestre.get_or_404(formsemestre_id)
     if edit:  # check permission
         if not formsemestre.can_edit_jury():
             raise ScoPermissionDenied(
diff --git a/app/views/jury_validations.py b/app/views/jury_validations.py
index e7678a2896820faaf1652530e2e62712a2de16c4..fe008e1498dfc2e4a49b9afc389d0e7131767518 100644
--- a/app/views/jury_validations.py
+++ b/app/views/jury_validations.py
@@ -94,7 +94,7 @@ def formsemestre_validation_etud_form(
     sortcol=None,
 ):
     "Formulaire choix jury pour un étudiant"
-    formsemestre: FormSemestre = FormSemestre.query.get_or_404(formsemestre_id)
+    formsemestre: FormSemestre = FormSemestre.get_or_404(formsemestre_id)
     read_only = not formsemestre.can_edit_jury()
     if formsemestre.formation.is_apc():
         return redirect(
@@ -128,7 +128,7 @@ def formsemestre_validation_etud(
     sortcol=None,
 ):
     "Enregistre choix jury pour un étudiant"
-    formsemestre: FormSemestre = FormSemestre.query.get_or_404(formsemestre_id)
+    formsemestre: FormSemestre = FormSemestre.get_or_404(formsemestre_id)
     if not formsemestre.can_edit_jury():
         raise ScoPermissionDenied(
             dest_url=url_for(
@@ -162,7 +162,7 @@ def formsemestre_validation_etud_manu(
     sortcol=None,
 ):
     "Enregistre choix jury pour un étudiant"
-    formsemestre: FormSemestre = FormSemestre.query.get_or_404(formsemestre_id)
+    formsemestre: FormSemestre = FormSemestre.get_or_404(formsemestre_id)
     if not formsemestre.can_edit_jury():
         raise ScoPermissionDenied(
             dest_url=url_for(
@@ -521,7 +521,7 @@ def formsemestre_validation_but(
 @permission_required(Permission.ScoView)
 def formsemestre_validation_auto_but(formsemestre_id: int = None):
     "Saisie automatique des décisions de jury BUT"
-    formsemestre: FormSemestre = FormSemestre.query.get_or_404(formsemestre_id)
+    formsemestre: FormSemestre = FormSemestre.get_or_404(formsemestre_id)
     if not formsemestre.can_edit_jury():
         raise ScoPermissionDenied(
             dest_url=url_for(
@@ -583,7 +583,7 @@ def formsemestre_validation_auto_but(formsemestre_id: int = None):
 @permission_required(Permission.ScoView)
 def formsemestre_validate_previous_ue(formsemestre_id, etudid=None):
     "Form. saisie UE validée hors ScoDoc"
-    formsemestre: FormSemestre = FormSemestre.query.get_or_404(formsemestre_id)
+    formsemestre: FormSemestre = FormSemestre.get_or_404(formsemestre_id)
     if not formsemestre.can_edit_jury():
         raise ScoPermissionDenied(
             dest_url=url_for(
@@ -610,7 +610,7 @@ def formsemestre_validate_previous_ue(formsemestre_id, etudid=None):
 @scodoc7func
 def formsemestre_ext_edit_ue_validations(formsemestre_id, etudid=None):
     "Form. edition UE semestre extérieur"
-    formsemestre: FormSemestre = FormSemestre.query.get_or_404(formsemestre_id)
+    formsemestre: FormSemestre = FormSemestre.get_or_404(formsemestre_id)
     if not formsemestre.can_edit_jury():
         raise ScoPermissionDenied(
             dest_url=url_for(
@@ -660,7 +660,7 @@ def formsemestre_validation_auto(formsemestre_id):
 @scodoc7func
 def do_formsemestre_validation_auto(formsemestre_id):
     "Formulaire saisie automatisee des decisions d'un semestre"
-    formsemestre: FormSemestre = FormSemestre.query.get_or_404(formsemestre_id)
+    formsemestre: FormSemestre = FormSemestre.get_or_404(formsemestre_id)
     if not formsemestre.can_edit_jury():
         raise ScoPermissionDenied(
             dest_url=url_for(
@@ -681,7 +681,7 @@ def formsemestre_validation_suppress_etud(
     formsemestre_id, etudid, dialog_confirmed=False
 ):
     """Suppression des décisions de jury pour un étudiant."""
-    formsemestre: FormSemestre = FormSemestre.query.get_or_404(formsemestre_id)
+    formsemestre: FormSemestre = FormSemestre.get_or_404(formsemestre_id)
     if not formsemestre.can_edit_jury():
         raise ScoPermissionDenied(
             dest_url=url_for(
@@ -884,7 +884,7 @@ def erase_decisions_annee_formation(etudid: int, formation_id: int, annee: int):
     validations = jury.erase_decisions_annee_formation(etud, formation, annee)
     formsemestre_origine_id = request.args.get("formsemestre_id")
     formsemestre_origine = (
-        FormSemestre.query.get_or_404(formsemestre_origine_id)
+        FormSemestre.get_or_404(formsemestre_origine_id)
         if formsemestre_origine_id
         else None
     )
diff --git a/app/views/notes.py b/app/views/notes.py
index a01c3dde62603a43fd51323b7a64604a2890ea62..b317169e9ac45893c78c03f9bdf21adfdb745c5d 100644
--- a/app/views/notes.py
+++ b/app/views/notes.py
@@ -456,8 +456,8 @@ def set_ue_niveau_competence():
     niveau_id = request.form.get("niveau_id")
     if niveau_id == "":
         niveau_id = None
-    ue: UniteEns = UniteEns.query.get_or_404(ue_id)
-    niveau = None if niveau_id is None else ApcNiveau.query.get_or_404(niveau_id)
+    ue: UniteEns = UniteEns.get_or_404(ue_id)
+    niveau = None if niveau_id is None else ApcNiveau.get_or_404(niveau_id)
     try:
         ue.set_niveau_competence(niveau)
     except ScoFormationConflict:
@@ -476,7 +476,7 @@ def get_ue_niveaux_options_html():
     if ue_id is None:
         log("WARNING: get_ue_niveaux_options_html missing ue_id arg")
         return "???"
-    ue: UniteEns = UniteEns.query.get_or_404(ue_id)
+    ue: UniteEns = UniteEns.get_or_404(ue_id)
     return apc_edit_ue.get_ue_niveaux_options_html(ue)
 
 
@@ -494,7 +494,7 @@ def ue_table(formation_id=None, semestre_idx=1, msg=""):
 @scodoc
 @permission_required(Permission.ScoView)
 def ue_infos(ue_id: int):
-    ue = UniteEns.query.get_or_404(ue_id)
+    ue = UniteEns.get_or_404(ue_id)
     return sco_edit_apc.html_ue_infos(ue)
 
 
@@ -901,7 +901,7 @@ sco_publish(
 @scodoc7func
 def formsemestre_flip_lock(formsemestre_id, dialog_confirmed=False):
     "Changement de l'état de verrouillage du semestre"
-    formsemestre: FormSemestre = FormSemestre.query.get_or_404(formsemestre_id)
+    formsemestre: FormSemestre = FormSemestre.get_or_404(formsemestre_id)
     dest_url = url_for(
         "notes.formsemestre_status",
         scodoc_dept=g.scodoc_dept,
@@ -1097,7 +1097,7 @@ def edit_moduleimpl_resp(moduleimpl_id: int):
     """Changement d'un enseignant responsable de module
     Accessible par Admin et dir des etud si flag resp_can_change_ens
     """
-    modimpl: ModuleImpl = ModuleImpl.query.get_or_404(moduleimpl_id)
+    modimpl: ModuleImpl = ModuleImpl.get_or_404(moduleimpl_id)
     modimpl.can_change_responsable(current_user, raise_exc=True)  # access control
     H = [
         f"""<h2 class="formsemestre">Modification du responsable du
@@ -1438,7 +1438,7 @@ def formsemestre_desinscription(etudid, formsemestre_id, dialog_confirmed=False)
     S'il s'agit d'un semestre extérieur et qu'il n'y a plus d'inscrit,
     le semestre sera supprimé.
     """
-    formsemestre: FormSemestre = FormSemestre.query.get_or_404(formsemestre_id)
+    formsemestre: FormSemestre = FormSemestre.get_or_404(formsemestre_id)
     sem = formsemestre.to_dict()  # compat
     # -- check lock
     if not formsemestre.etat:
@@ -1531,7 +1531,7 @@ def etud_desinscrit_ue(etudid, formsemestre_id, ue_id):
     - En APC: dispense de l'UE indiquée.
     """
     etud = Identite.get_etud(etudid)
-    ue = UniteEns.query.get_or_404(ue_id)
+    ue = UniteEns.get_or_404(ue_id)
     formsemestre = FormSemestre.get_formsemestre(formsemestre_id)
     if ue.formation.is_apc():
         if (
@@ -1582,7 +1582,7 @@ def etud_inscrit_ue(etudid, formsemestre_id, ue_id):
         id=formsemestre_id, dept_id=g.scodoc_dept_id
     ).first_or_404()
     etud = Identite.get_etud(etudid)
-    ue = UniteEns.query.get_or_404(ue_id)
+    ue = UniteEns.get_or_404(ue_id)
     if ue.formation.is_apc():
         for disp in DispenseUE.query.filter_by(
             formsemestre_id=formsemestre_id, etudid=etud.id, ue_id=ue_id
@@ -2268,7 +2268,7 @@ def appreciation_add_form(
         edit = 1
     else:
         edit = 0
-    formsemestre: FormSemestre = FormSemestre.query.get_or_404(formsemestre_id)
+    formsemestre: FormSemestre = FormSemestre.get_or_404(formsemestre_id)
     # check custom access permission
     can_edit_app = formsemestre.est_responsable(current_user) or (
         current_user.has_permission(Permission.EtudInscrit)
diff --git a/app/views/pn_modules.py b/app/views/pn_modules.py
index 01f06ac875ad2b11574a632d1306f1a7893a6cd3..777808bea1bb9015d614ed1da5ed8c4b7c320a9d 100644
--- a/app/views/pn_modules.py
+++ b/app/views/pn_modules.py
@@ -59,13 +59,13 @@ def table_modules_ue_coefs(formation_id, semestre_idx=None, parcours_id: int = N
     Si le parcours est indiqué et que la formation a un référentiel de compétence,
     restreint l'affichage aux UE et modules de ce parcours.
     """
-    formation: Formation = models.Formation.query.get_or_404(formation_id)  # check
+    formation: Formation = models.Formation.get_or_404(formation_id)  # check
     if semestre_idx == "":
         semestre_idx = None
     if parcours_id == "":
         parcours_id = None
     if parcours_id is not None and formation.referentiel_competence is not None:
-        parcour: ApcParcours = ApcParcours.query.get_or_404(parcours_id)
+        parcour: ApcParcours = ApcParcours.get_or_404(parcours_id)
     else:
         parcour = None
     df, ues, modules = moy_ue.df_load_module_coefs(formation_id, semestre_idx)
diff --git a/app/views/refcomp.py b/app/views/refcomp.py
index 420ad7e8f6758db9e330c68467c7c46aa7dbb697..1ed83606f1b5fdfb78bd1858e410323fdf85a92f 100644
--- a/app/views/refcomp.py
+++ b/app/views/refcomp.py
@@ -46,9 +46,7 @@ from app.views import ScoData
 @as_json
 def refcomp(refcomp_id):
     """Le référentiel de compétences, en JSON."""
-    ref: ApcReferentielCompetences = ApcReferentielCompetences.query.get_or_404(
-        refcomp_id
-    )
+    ref: ApcReferentielCompetences = ApcReferentielCompetences.get_or_404(refcomp_id)
     return ref.to_dict()
 
 
@@ -58,7 +56,7 @@ def refcomp(refcomp_id):
 def refcomp_show(refcomp_id):
     """Affichage du référentiel de compétences."""
     referentiel_competence: ApcReferentielCompetences = (
-        ApcReferentielCompetences.query.get_or_404(refcomp_id)
+        ApcReferentielCompetences.get_or_404(refcomp_id)
     )
     # Autres référentiels "équivalents" pour proposer de changer les formations:
     referentiels_equivalents = referentiel_competence.equivalents()
@@ -82,7 +80,7 @@ def refcomp_show(refcomp_id):
 @permission_required(Permission.EditFormation)
 def refcomp_delete(refcomp_id):
     """Suppression du référentiel de la base. Le fichier source n'est pas affecté."""
-    ref = ApcReferentielCompetences.query.get_or_404(refcomp_id)
+    ref = ApcReferentielCompetences.get_or_404(refcomp_id)
     db.session.delete(ref)
     db.session.commit()
     flash("référentiel de compétences supprimé")
@@ -148,7 +146,7 @@ def refcomp_table():
 @permission_required(Permission.EditFormation)
 def refcomp_assoc_formation(formation_id: int):
     """Formulaire association ref. compétence"""
-    formation = Formation.query.get_or_404(formation_id)
+    formation = Formation.get_or_404(formation_id)
     form = FormationRefCompForm()
     form.referentiel_competence.choices = [
         (r.id, f"{r.type_titre} {r.specialite_long} ({r.get_version()})")
@@ -193,7 +191,7 @@ def refcomp_assoc_formation(formation_id: int):
 @permission_required(Permission.EditFormation)
 def refcomp_desassoc_formation(formation_id: int):
     """Désassocie la formation de son ref. de compétence"""
-    formation: Formation = Formation.query.get_or_404(formation_id)
+    formation: Formation = Formation.get_or_404(formation_id)
     formation.refcomp_desassoc()
     return redirect(
         url_for("notes.ue_table", scodoc_dept=g.scodoc_dept, formation_id=formation.id)
diff --git a/app/views/scodoc.py b/app/views/scodoc.py
index fcdf857f973945f1c5a868b724e5d7b7eb3ecdfb..05980c452556aa65c7e7bb069aaeb4491039f231 100644
--- a/app/views/scodoc.py
+++ b/app/views/scodoc.py
@@ -139,7 +139,7 @@ def create_dept():
 @admin_required
 def toggle_dept_vis(dept_id):
     """Cache ou rend visible un dept"""
-    dept = Departement.query.get_or_404(dept_id)
+    dept = Departement.get_or_404(dept_id)
     dept.visible = not dept.visible
     db.session.add(dept)
     db.session.commit()
@@ -568,7 +568,7 @@ def get_etud_dept():
             # le choix a peu d'importance...
             last_etud = etuds[-1]
 
-    return Departement.query.get_or_404(last_etud.dept_id).acronym
+    return Departement.get_or_404(last_etud.dept_id).acronym
 
 
 @bp.route("/ScoDoc/about")
diff --git a/app/views/scolar.py b/app/views/scolar.py
index 5ddaf4c932a8b2125f38148a12e8bed2e6636bb3..ae2c6ef981b017a20c254e633cc489afa5a5240c 100644
--- a/app/views/scolar.py
+++ b/app/views/scolar.py
@@ -879,7 +879,7 @@ sco_publish(
 @permission_required(Permission.ScoView)
 def groups_auto_repartition(partition_id: int):
     "Réparti les etudiants dans des groupes dans une partition"
-    partition: Partition = Partition.query.get_or_404(partition_id)
+    partition: Partition = Partition.get_or_404(partition_id)
     return sco_groups.groups_auto_repartition(partition)
 
 
@@ -942,7 +942,7 @@ def partition_editor(formsemestre_id: int, edit_partition=False):
     """Page édition groupes et partitions
     Si edit_partition, se met en mode édition des partitions.
     """
-    formsemestre: FormSemestre = FormSemestre.query.get_or_404(formsemestre_id)
+    formsemestre: FormSemestre = FormSemestre.get_or_404(formsemestre_id)
     edit_partition = bool(int(edit_partition)) if edit_partition else False
     formsemestre.setup_parcours_groups()
     return render_template(
@@ -978,7 +978,7 @@ def students_groups_auto_assignment(formsemestre_id: int):
 def create_partition_parcours(formsemestre_id):
     """Création d'une partitions nommée "Parcours" (PARTITION_PARCOURS)
     avec un groupe par parcours."""
-    formsemestre: FormSemestre = FormSemestre.query.get_or_404(formsemestre_id)
+    formsemestre: FormSemestre = FormSemestre.get_or_404(formsemestre_id)
     formsemestre.setup_parcours_groups()
     return flask.redirect(
         url_for(
@@ -1830,7 +1830,7 @@ def etud_copy_in_other_dept(etudid: int):
         except ValueError:
             log("etud_copy_in_other_dept: invalid formsemestre_id")
             abort(404, description="formsemestre_id invalide")
-        formsemestre: FormSemestre = FormSemestre.query.get_or_404(formsemestre_id)
+        formsemestre: FormSemestre = FormSemestre.get_or_404(formsemestre_id)
         if not current_user.has_permission(
             Permission.EtudInscrit, formsemestre.departement.acronym
         ):
diff --git a/tests/unit/test_caches.py b/tests/unit/test_caches.py
index 0e3766ba04e3c374696b809cc55ecb145988c7b7..9ba161d9b58c09e8eec06dedf952dd5d195ba6a9 100644
--- a/tests/unit/test_caches.py
+++ b/tests/unit/test_caches.py
@@ -37,7 +37,7 @@ def test_notes_table(test_client):  # XXX A REVOIR POUR TESTER RES TODO
     assert len(sems)
     sem = sems[0]
     formsemestre_id = sem["formsemestre_id"]
-    formsemestre: FormSemestre = FormSemestre.query.get_or_404(formsemestre_id)
+    formsemestre: FormSemestre = FormSemestre.get_or_404(formsemestre_id)
     nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
     assert nt
     assert sco_cache.ResultatsSemestreCache.get(formsemestre_id)
diff --git a/tests/unit/test_formations.py b/tests/unit/test_formations.py
index 5e193567ab6e155cc6307c67ff82eb94ac3c3709..b1634032ca755ad8d0d4c96f28dc084ce22e2444 100644
--- a/tests/unit/test_formations.py
+++ b/tests/unit/test_formations.py
@@ -339,7 +339,7 @@ def test_import_formation(test_client, filename="formation-exemple-1.xml"):
             module_id=mod.id,
             formsemestre_id=formsemestre_ids[mod.semestre_id - 1],
         )
-        mi = db.session.query(ModuleImpl).get(moduleimpl_id)
+        mi = db.session.get(ModuleImpl, moduleimpl_id)
         assert mi.module_id == mod.id
 
     # --- Export formation en XML
diff --git a/tests/unit/test_notes_modules.py b/tests/unit/test_notes_modules.py
index 7a5a9ea565e406248eae0980172056837a551be7..46439d070d8e65f1e9d710cb18fd974f170e8116 100644
--- a/tests/unit/test_notes_modules.py
+++ b/tests/unit/test_notes_modules.py
@@ -33,7 +33,7 @@ def check_nt(
     (peut changer dans le futur, ne pas utiliser hors ScoDoc !)
     ne vérifie que les valeurs expected non False
     """
-    formsemestre: FormSemestre = FormSemestre.query.get_or_404(formsemestre_id)
+    formsemestre: FormSemestre = FormSemestre.get_or_404(formsemestre_id)
     nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
     mod_moy = nt.get_etud_mod_moy(moduleimpl_id, etudid)
     if expected_moy_ue is not False:
@@ -276,7 +276,7 @@ def test_notes_modules(test_client):
         module_id=module_id2,
         formsemestre_id=formsemestre_id,
     )
-    formsemestre: FormSemestre = FormSemestre.query.get_or_404(formsemestre_id)
+    formsemestre: FormSemestre = FormSemestre.get_or_404(formsemestre_id)
     # Pour prendre en compte l'ajout au vol d'un moduleimpl:
     del formsemestre.modimpls_sorted
 
@@ -302,7 +302,7 @@ def test_notes_modules(test_client):
         {"etudid": etuds[1]["etudid"], "moduleimpl_id": moduleimpl_id2},
         formsemestre_id=formsemestre_id,
     )
-    formsemestre: FormSemestre = FormSemestre.query.get_or_404(formsemestre_id)
+    formsemestre: FormSemestre = FormSemestre.get_or_404(formsemestre_id)
     nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
     ue_status = nt.get_etud_ue_status(etudid, ue_id)
 
@@ -314,7 +314,7 @@ def test_notes_modules(test_client):
         coefficient=1.0,
     )
     _ = G.create_note(evaluation_id=e_m2["evaluation_id"], etudid=etudid, note=19.5)
-    formsemestre: FormSemestre = FormSemestre.query.get_or_404(formsemestre_id)
+    formsemestre: FormSemestre = FormSemestre.get_or_404(formsemestre_id)
     nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
     ue_status = nt.get_etud_ue_status(etudid, ue_id)
 
diff --git a/tests/unit/test_sco_basic.py b/tests/unit/test_sco_basic.py
index f1d933fdb775295bcaa8343dbf1db090b99330f7..35165871e0879a8a72f94303f4bd3a3dfb810f91 100644
--- a/tests/unit/test_sco_basic.py
+++ b/tests/unit/test_sco_basic.py
@@ -227,7 +227,7 @@ def run_sco_basic(verbose=False, dept=None) -> FormSemestre:
             redirect=False,
         )
     # Vérifie que toutes les UE des étudiants notés ont été acquises:
-    formsemestre: FormSemestre = FormSemestre.query.get_or_404(formsemestre_id)
+    formsemestre: FormSemestre = FormSemestre.get_or_404(formsemestre_id)
     nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
     for etud in etuds[:5]:
         dec_ues = nt.get_etud_decisions_ue(etud["etudid"])
diff --git a/tools/fakedatabase/create_test_api_database.py b/tools/fakedatabase/create_test_api_database.py
index b3ee1590ced5ee22c6f51af21b82be8bd5d1a11e..6220ecb9d01387bcc9aefc7be0febb84f3e72b02 100644
--- a/tools/fakedatabase/create_test_api_database.py
+++ b/tools/fakedatabase/create_test_api_database.py
@@ -311,7 +311,7 @@ def saisie_notes_evaluations(formsemestre: FormSemestre, user: User):
     for ue in list_ues:
         mods = ue.modules
         for mod in mods:
-            moduleimpl = ModuleImpl.query.get_or_404(mod.id)
+            moduleimpl = ModuleImpl.get_or_404(mod.id)
             for evaluation in moduleimpl.evaluations:
                 condition_saisie_notes = random.randint(0, 2)
                 saisir_notes(evaluation.id, condition_saisie_notes)