From 169b720620895ba6e1333874732c8b0079152b4b Mon Sep 17 00:00:00 2001 From: william Date: Thu, 27 Aug 2026 17:31:58 +0200 Subject: [PATCH] =?UTF-8?q?Corrige=20la=20sauvegarde=20des=20donn=C3=A9es?= =?UTF-8?q?=20d'un=20objet=20:=20collision=20d'id=20de=20formulaire=20entr?= =?UTF-8?q?e=20objets?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 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 --- templates/game_dashboard.html | 20 ++++++------- tests/test_data_form_id_collision.py | 42 ++++++++++++++++++++++++++++ 2 files changed, 52 insertions(+), 10 deletions(-) create mode 100644 tests/test_data_form_id_collision.py diff --git a/templates/game_dashboard.html b/templates/game_dashboard.html index 1fce0814..b4b3842c 100644 --- a/templates/game_dashboard.html +++ b/templates/game_dashboard.html @@ -453,8 +453,8 @@ {% set rows = rows_by_definition[d.id] %} {% if rows %} {% for r in rows %} -
-
+
{% endfor %}
@@ -470,18 +470,18 @@ {% set col = f.name|colname %} {% if f.type == 'texte' %} - + {% elif f.type == 'texte_long' %} - + {% elif f.type in ('nombre_entier', 'nombre_decimal') %} - + {% elif f.type == 'booleen' %} - + {% elif f.type == 'date' %} - + {% elif f.type == 'relation' %}
- {% for opt in relation_options_by_definition[d.id].get(f.id, []) %} @@ -493,8 +493,8 @@ {% endfor %} - - + + {% endfor %} diff --git a/tests/test_data_form_id_collision.py b/tests/test_data_form_id_collision.py new file mode 100644 index 00000000..c799ae58 --- /dev/null +++ b/tests/test_data_form_id_collision.py @@ -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
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"