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
+10 -10
View File
@@ -453,8 +453,8 @@
{% set rows = rows_by_definition[d.id] %}
{% if rows %}
{% for r in rows %}
<form method="post" action="{{ url_for('data_edit', slug=game.slug, definition_id=d.id, row_id=r.id) }}" id="dataEditForm{{ r.id }}"></form>
<form method="post" action="{{ url_for('data_delete', slug=game.slug, definition_id=d.id, row_id=r.id) }}" id="dataDeleteForm{{ r.id }}"
<form method="post" action="{{ url_for('data_edit', slug=game.slug, definition_id=d.id, row_id=r.id) }}" id="dataEditForm{{ d.id }}-{{ r.id }}"></form>
<form method="post" action="{{ url_for('data_delete', slug=game.slug, definition_id=d.id, row_id=r.id) }}" id="dataDeleteForm{{ d.id }}-{{ r.id }}"
onsubmit="return confirm('Supprimer définitivement l\'entrée #{{ r.id }} ?');"></form>
{% endfor %}
<div class="fieldsTableWrap">
@@ -470,18 +470,18 @@
{% set col = f.name|colname %}
<td data-col="field-{{ f.id }}" data-field-label="{{ f.name }}">
{% if f.type == 'texte' %}
<input class="input is-small" type="text" form="dataEditForm{{ r.id }}" name="{{ col }}" value="{{ r[col] or '' }}">
<input class="input is-small" type="text" form="dataEditForm{{ d.id }}-{{ r.id }}" name="{{ col }}" value="{{ r[col] or '' }}">
{% elif f.type == 'texte_long' %}
<textarea class="textarea is-small" form="dataEditForm{{ r.id }}" name="{{ col }}" rows="1">{{ r[col] or '' }}</textarea>
<textarea class="textarea is-small" form="dataEditForm{{ d.id }}-{{ r.id }}" name="{{ col }}" rows="1">{{ r[col] or '' }}</textarea>
{% elif f.type in ('nombre_entier', 'nombre_decimal') %}
<input class="input is-small" type="number" step="{{ '1' if f.type == 'nombre_entier' else 'any' }}" form="dataEditForm{{ r.id }}" name="{{ col }}" value="{{ r[col] if r[col] is not none else '' }}">
<input class="input is-small" type="number" step="{{ '1' if f.type == 'nombre_entier' else 'any' }}" form="dataEditForm{{ d.id }}-{{ r.id }}" name="{{ col }}" value="{{ r[col] if r[col] is not none else '' }}">
{% elif f.type == 'booleen' %}
<label class="checkbox"><input type="checkbox" form="dataEditForm{{ r.id }}" name="{{ col }}" {{ 'checked' if r[col] else '' }}></label>
<label class="checkbox"><input type="checkbox" form="dataEditForm{{ d.id }}-{{ r.id }}" name="{{ col }}" {{ 'checked' if r[col] else '' }}></label>
{% elif f.type == 'date' %}
<input class="input is-small" type="date" form="dataEditForm{{ r.id }}" name="{{ col }}" value="{{ r[col] or '' }}">
<input class="input is-small" type="date" form="dataEditForm{{ d.id }}-{{ r.id }}" name="{{ col }}" value="{{ r[col] or '' }}">
{% elif f.type == 'relation' %}
<div class="select is-small">
<select form="dataEditForm{{ r.id }}" name="{{ col }}_id">
<select form="dataEditForm{{ d.id }}-{{ r.id }}" name="{{ col }}_id">
<option value="">—</option>
{% for opt in relation_options_by_definition[d.id].get(f.id, []) %}
<option value="{{ opt.id }}" {{ 'selected' if r[col ~ '_id'] == opt.id else '' }}>{{ opt.label }}</option>
@@ -493,8 +493,8 @@
{% endfor %}
<td class="fieldsTableActions">
<button type="button" class="button is-small" title="Voir cette entrée en détail" onclick="openRowDetailPanel({{ d.id }}, {{ r.id }})">👁️</button>
<button type="submit" form="dataEditForm{{ r.id }}" class="button is-small">Enregistrer</button>
<button type="submit" form="dataDeleteForm{{ r.id }}" class="button is-small is-danger">🗑️</button>
<button type="submit" form="dataEditForm{{ d.id }}-{{ r.id }}" class="button is-small">Enregistrer</button>
<button type="submit" form="dataDeleteForm{{ d.id }}-{{ r.id }}" class="button is-small is-danger">🗑️</button>
</td>
</tr>
{% endfor %}