Corrige la sauvegarde des données d'un objet : collision d'id de formulaire entre objets

Bug rapporté : modifier une donnée dans l'onglet "Données" d'un objet
redirigeait vers le panneau d'un AUTRE objet (le premier de la liste) et
n'enregistrait rien sur le bon objet.

Cause : chaque objet a sa propre table SQLite (db/rows/insert_row.py), donc
les ids de ses lignes repartent de 1 - deux objets ont chacun une ligne
#1, #2, etc. Or game_dashboard.html générait les formulaires d'édition/
suppression d'une ligne avec un id DOM basé seulement sur r.id
("dataEditForm{{r.id}}"), jamais sur l'objet auquel elle appartient. Les
<input form="dataEditForm1"> de DEUX objets différents pointaient donc
vers le même id de formulaire dupliqué dans le document - et un id HTML
dupliqué se résout vers le PREMIER élément trouvé (le premier objet listé),
pas celui réellement affiché sous les yeux de l'utilisateur.

Correctif : les ids de formulaires ("dataEditForm"/"dataDeleteForm") et les
attributs form="..." des champs sont maintenant scopés par objet ET par
ligne ("dataEditForm{{d.id}}-{{r.id}}"), comme c'était déjà le cas pour les
panneaux (objectEditPanel, addEntryPanel...) via data-definition-id.

Nouveau test (tests/test_data_form_id_collision.py) : reproduit le
scénario exact (deux objets ayant chacun une ligne #1) et vérifie que les
ids de formulaire sont bien distincts et que la modification du second
objet ne touche pas le premier - confirmé en échec sur l'ancien code
(git stash) puis au vert avec le correctif. 128 tests au vert au total.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
william
2026-08-27 17:31:58 +02:00
co-authored by Claude Sonnet 5
parent 82a14fcb2b
commit 169b720620
2 changed files with 52 additions and 10 deletions
+42
View File
@@ -0,0 +1,42 @@
"""Régression : chaque objet a sa propre table SQLite, donc ses ids de
ligne repartent de 1 (db/rows/insert_row.py) — les <form id="dataEditForm
{{r.id}}"> de l'onglet Données (game_dashboard.html) doivent donc être
scopés par objet (dataEditForm{{d.id}}-{{r.id}}), sinon deux objets ayant
chacun une ligne #1 partagent le même id de formulaire dans le DOM et le
navigateur soumet vers le premier objet trouvé, pas celui qu'on modifie
réellement."""
import re
def _create_object(client, slug, name, field_name, field_type="texte"):
resp = client.post(f"/game/{slug}/objects/new", data={
"object_name": name, "field_name[]": [field_name], "field_type[]": [field_type],
"field_relation[]": [""], "field_required[]": ["0"], "field_min[]": [""], "field_max[]": [""],
}, follow_redirects=False)
return int(resp.headers["Location"].rstrip("/").split("/")[-1])
def test_data_edit_form_ids_are_scoped_per_object(client, game):
def_a = _create_object(client, game, "Jauge", "valeur", "nombre_entier")
def_b = _create_object(client, game, "Autre", "nom", "texte")
# Chaque objet a sa PROPRE table -> sa première ligne a id=1 dans les
# deux cas, exactement le scénario qui provoquait la collision.
client.post(f"/game/{game}/objects/{def_a}/data/new", data={"valeur": "10"})
client.post(f"/game/{game}/objects/{def_b}/data/new", data={"nom": "Bob"})
html = client.get(f"/game/{game}?tab=objects").get_data(as_text=True)
assert f'id="dataEditForm{def_a}-1"' in html
assert f'id="dataEditForm{def_b}-1"' in html
# Les deux formulaires doivent avoir des ids DISTINCTS malgré le même row id.
assert html.count('id="dataEditForm') == len(set(re.findall(r'id="(dataEditForm[^"]+)"', html)))
# Modifier la donnée du DEUXIÈME objet doit bien mettre à jour def_b,
# sans toucher def_a.
client.post(f"/game/{game}/objects/{def_b}/data/1/edit", data={"nom": "Alice"})
import db
row_a = db.list_rows(game, db.get_definition(game, def_a))[0]
row_b = db.list_rows(game, db.get_definition(game, def_b))[0]
assert row_a["valeur"] == 10
assert row_b["nom"] == "Alice"