Bug remonté : supprimer un élément plantait avec sqlite3.IntegrityError: FOREIGN KEY constraint failed. Cause : trigger_element_id/target_element_id (_flow_nodes, voir ensure_flow_schema.py - un nœud "clic sur cet élément" ou "Modifier cet élément"/"Activer cet onglet") et target_element_id (l'ancien système _actions, conservé pour compatibilité) référencent _screen_elements(id) SANS ON DELETE CASCADE - volontairement, un élément ne doit pas pouvoir disparaître "par erreur" en cascade depuis un nœud de logique qu'on modifie ailleurs. Mais ça veut dire que delete_element.py, qui ne supprimait jusqu'ici que la ligne elle-même, faisait échouer PRAGMA foreign_keys=ON (db/connection.py) dès qu'un nœud de logique existant référençait encore l'élément. Fix : delete_element.py nettoie maintenant ces références AVANT de supprimer l'élément - pas seulement pour l'élément explicitement supprimé, mais pour tous ses DESCENDANTS aussi (leur suppression est cascadée automatiquement au niveau SQL via parent_id, sans repasser par ce fichier, donc sans ce nettoyage si on ne le fait pas explicitement). Ajoute tests/test_delete_element_referenced_by_flow.py (élément référencé comme déclencheur, comme cible d'action, et cas d'un conteneur supprimé dont un descendant est référencé). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
57 lines
2.4 KiB
Python
57 lines
2.4 KiB
Python
import db
|
|
|
|
from ..flow.ensure_flow_schema import ensure_flow_schema
|
|
|
|
|
|
def _collect_ids(conn, screen_id, root_id):
|
|
"""Rassemble l'id de cet élément et de TOUS ses descendants — ceux
|
|
qu'une suppression en cascade (parent_id ON DELETE CASCADE, voir
|
|
ensure_schema.py) va de toute façon supprimer automatiquement en même
|
|
temps, sans passer par ce fichier. Nécessaire pour nettoyer AUSSI les
|
|
références de la Logique de la scène vers un simple DESCENDANT, pas
|
|
seulement vers l'élément explicitement supprimé."""
|
|
rows = conn.execute(
|
|
"SELECT id, parent_id FROM _screen_elements WHERE screen_id = ?", (screen_id,)
|
|
).fetchall()
|
|
children_of = {}
|
|
for r in rows:
|
|
if r["parent_id"]:
|
|
children_of.setdefault(r["parent_id"], []).append(r["id"])
|
|
ids = [root_id]
|
|
stack = [root_id]
|
|
while stack:
|
|
current = stack.pop()
|
|
for child_id in children_of.get(current, []):
|
|
ids.append(child_id)
|
|
stack.append(child_id)
|
|
return ids
|
|
|
|
|
|
def delete_element(slug, element_id):
|
|
ensure_flow_schema(slug)
|
|
conn = db.connect(slug)
|
|
row = conn.execute("SELECT screen_id FROM _screen_elements WHERE id = ?", (element_id,)).fetchone()
|
|
if not row:
|
|
conn.close()
|
|
return
|
|
ids = _collect_ids(conn, row["screen_id"], element_id)
|
|
placeholders = ",".join("?" * len(ids))
|
|
# Un nœud de la Logique de la scène (déclencheur "clic sur cet
|
|
# élément"/action "Modifier cet élément"...) ou une ancienne action du
|
|
# système _actions (conservé pour compatibilité) qui référence
|
|
# l'élément supprimé OU L'UN DE SES DESCENDANTS n'a plus aucun sens une
|
|
# fois l'élément disparu — et ces colonnes (trigger_element_id/
|
|
# target_element_id) n'ont volontairement PAS de ON DELETE CASCADE (un
|
|
# élément ne doit pas pouvoir être supprimé "par erreur" en cascade
|
|
# depuis un nœud de logique qu'on modifie). Sans ce nettoyage préalable,
|
|
# PRAGMA foreign_keys=ON (voir db/connection.py) fait échouer la
|
|
# suppression elle-même avec "FOREIGN KEY constraint failed".
|
|
conn.execute(
|
|
f"DELETE FROM _flow_nodes WHERE trigger_element_id IN ({placeholders}) OR target_element_id IN ({placeholders})",
|
|
ids + ids,
|
|
)
|
|
conn.execute(f"DELETE FROM _actions WHERE target_element_id IN ({placeholders})", ids)
|
|
conn.execute("DELETE FROM _screen_elements WHERE id = ?", (element_id,))
|
|
conn.commit()
|
|
conn.close()
|