From 4585a72588a8dbde39030765948d294187f93259 Mon Sep 17 00:00:00 2001 From: william Date: Sun, 30 Aug 2026 15:54:37 +0200 Subject: [PATCH] Phase 1 (3/3) : bascule "par joueur" dans le tableau de bord MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Dernier morceau du plan d'état par joueur : le créateur choisit, à la création d'une variable globale ou d'un objet, si chaque joueur aura sa propre valeur/ses propres lignes (coché par défaut) ou si elle est explicitement PARTAGÉE par tous les joueurs (ex. un compteur de visiteurs global, un catalogue commun) — voir db/global_vars/create_global_variable.py et db/definitions/create_definition.py (Phase 1, 1er commit). routes/global_vars/create_global_var.py, routes/objects/object_new.py : lisent la case à cocher "per_player" du formulaire (absente => reste per_player=1, comportement par défaut). templates/game_dashboard.html : case à cocher sur les deux panneaux de création + colonne "Par joueur" dans les deux tableaux existants, pour que ce réglage (immuable après création, comme le nom d'une variable) reste visible. db/definitions/list_definitions.py appelait _definitions directement sans jamais migrer son schéma — un tableau de bord ouvert avant la toute première création/modification d'objet aurait affiché "Non — partagé" pour un objet en réalité per_player=1 (colonne absente => Undefined, donc faux en Jinja) : corrigé en appelant ensure_field_bounds_schema() ici aussi, comme le fait déjà create_definition.py/get_definition.py. Vérifié : 239 tests passent (2 nouveaux, dont un qui aurait détecté le bug ci-dessus). Phase 1 (état par joueur) est maintenant complète : couche db/, route publique /jouer/, et ce réglage créateur. Co-Authored-By: Claude Sonnet 5 --- db/definitions/list_definitions.py | 2 ++ routes/global_vars/create_global_var.py | 7 ++++++- routes/objects/object_new.py | 7 ++++++- templates/game_dashboard.html | 25 +++++++++++++++++++++++-- tests/test_player_state.py | 23 +++++++++++++++++++++++ 5 files changed, 60 insertions(+), 4 deletions(-) diff --git a/db/definitions/list_definitions.py b/db/definitions/list_definitions.py index ed28d9fd..526d501e 100644 --- a/db/definitions/list_definitions.py +++ b/db/definitions/list_definitions.py @@ -1,7 +1,9 @@ from ..connection import connect +from .ensure_field_bounds_schema import ensure_field_bounds_schema def list_definitions(slug): + ensure_field_bounds_schema(slug) conn = connect(slug) rows = conn.execute("SELECT * FROM _definitions ORDER BY id").fetchall() conn.close() diff --git a/routes/global_vars/create_global_var.py b/routes/global_vars/create_global_var.py index 4f50a41f..37158678 100644 --- a/routes/global_vars/create_global_var.py +++ b/routes/global_vars/create_global_var.py @@ -13,6 +13,11 @@ def create_global_var(slug): name = (request.form.get("name") or "").strip() var_type = request.form.get("type") or "texte" default_value = request.form.get("default_value") or "" + # État par joueur (Phase 1) : coché par défaut (une case DÉCOCHÉE + # explicitement rend la variable partagée par tous les joueurs, voir + # db/global_vars/create_global_variable.py) — absent du formulaire + # (jeux/pages qui n'ont pas encore ce champ) => reste per_player. + per_player = request.form.get("per_player", "1") == "1" if name: - db.create_global_variable(slug, name, var_type, default_value) + db.create_global_variable(slug, name, var_type, default_value, per_player=per_player) return redirect(url_for("game_dashboard", slug=slug, tab="variables")) diff --git a/routes/objects/object_new.py b/routes/objects/object_new.py index 98c0bbc0..2a76a90c 100644 --- a/routes/objects/object_new.py +++ b/routes/objects/object_new.py @@ -23,8 +23,13 @@ def object_new(slug): # message d'erreur, juste un retour au tableau de bord) dès qu'on # tapait le nom sans avoir encore cliqué "+ Ajouter un champ". fields = _parse_field_rows(request.form) + # État par joueur (Phase 1) : coché par défaut (une case DÉCOCHÉE + # explicitement rend l'objet partagé par tous les joueurs — ex. un + # catalogue/une liste de questions — voir db/definitions/ + # create_definition.py). + per_player = request.form.get("per_player", "1") == "1" if name: - definition_id = db.create_definition(slug, name, fields) + definition_id = db.create_definition(slug, name, fields, per_player=per_player) return redirect(url_for("object_view", slug=slug, definition_id=definition_id)) return redirect(url_for("game_dashboard", slug=slug)) diff --git a/templates/game_dashboard.html b/templates/game_dashboard.html index 545abb84..b1f0ed36 100644 --- a/templates/game_dashboard.html +++ b/templates/game_dashboard.html @@ -76,12 +76,13 @@ {% if definitions %}
- + {% for d in definitions %} + @@ -158,6 +159,15 @@ + + {% if variables %} @@ -166,7 +176,7 @@ {% endfor %}
NomTable SQL
NomTable SQLPar joueur
{{ d.table_name }}{{ 'Oui' if d.per_player else 'Non — partagé' }}
- + {% for v in variables %} @@ -188,6 +198,7 @@ {% endif %} +
NomTypeValeur
NomTypeValeurPar joueur
{{ 'Oui' if v.per_player else 'Non — partagée' }}
+ + +

Champs

diff --git a/tests/test_player_state.py b/tests/test_player_state.py index 9ba385b4..0034de7a 100644 --- a/tests/test_player_state.py +++ b/tests/test_player_state.py @@ -97,6 +97,29 @@ def test_get_row_does_not_leak_across_players(game): assert db.get_row(game, definition, row_id, player_id="alice") is not None +def test_create_global_var_route_respects_per_player_checkbox(client, game): + client.post(f"/game/{game}/variables/new", data={ + "name": "score", "type": "nombre_entier", "default_value": "0", + }) + assert db.list_global_variables(game)[0]["per_player"] == 1 + + client.post(f"/game/{game}/variables/new", data={ + "name": "compteur_visites", "type": "nombre_entier", "default_value": "0", "per_player": "0", + }) + variables = {v["name"]: v for v in db.list_global_variables(game)} + assert variables["compteur_visites"]["per_player"] == 0 + + +def test_object_new_route_respects_per_player_checkbox(client, game): + client.post(f"/game/{game}/objects/new", data={"object_name": "Inventaire"}) + definitions = {d["name"]: d for d in db.list_definitions(game)} + assert definitions["Inventaire"]["per_player"] == 1 + + client.post(f"/game/{game}/objects/new", data={"object_name": "Catalogue", "per_player": "0"}) + definitions = {d["name"]: d for d in db.list_definitions(game)} + assert definitions["Catalogue"]["per_player"] == 0 + + def test_creator_preview_default_matches_shared_definition_behavior(game): """L'aperçu créateur (aucun player_id passé nulle part, donc PLAYER_SHARED partout) doit continuer de fonctionner exactement comme