Dev #3
@@ -8,11 +8,17 @@ from core.flask_app import app
|
||||
@app.route("/game/<slug>/objects/<int:definition_id>/fields/add", methods=["POST"])
|
||||
def object_field_add(slug, definition_id):
|
||||
name = request.form.get("field_name", "").strip()
|
||||
if name:
|
||||
field_type = request.form.get("field_type", "texte")
|
||||
relation_definition_id = request.form.get("field_relation")
|
||||
# Voir parse_field_rows.py (même garde) : "Relation" choisi sans objet
|
||||
# cible valide ferait planter add_field_to_definition (int("") lève),
|
||||
# ce qui annulait l'ALTER TABLE avant son commit() -- le champ
|
||||
# n'était alors ajouté du tout, sans le moindre message d'erreur.
|
||||
if name and not (field_type == "relation" and not (relation_definition_id or "").strip().isdigit()):
|
||||
db.add_field_to_definition(slug, definition_id, {
|
||||
"name": name,
|
||||
"type": request.form.get("field_type", "texte"),
|
||||
"relation_definition_id": request.form.get("field_relation"),
|
||||
"type": field_type,
|
||||
"relation_definition_id": relation_definition_id,
|
||||
"required": request.form.get("field_required") == "1",
|
||||
"min_value": request.form.get("field_min") or None,
|
||||
"max_value": request.form.get("field_max") or None,
|
||||
|
||||
@@ -9,10 +9,26 @@ def _parse_field_rows(form):
|
||||
for i, fname in enumerate(field_names):
|
||||
if not fname.strip():
|
||||
continue
|
||||
ftype = field_types[i]
|
||||
relation_definition_id = field_relations[i] if i < len(field_relations) else None
|
||||
if ftype == "relation" and not (relation_definition_id or "").strip().isdigit():
|
||||
# Type "Relation vers un autre objet" choisi sans (ou avant
|
||||
# d'avoir pu) sélectionner d'objet cible réel — ex. le tout
|
||||
# premier objet créé dans un jeu, quand ce sélecteur est encore
|
||||
# vide faute d'un autre objet à pointer. create_definition()
|
||||
# fait un int(relation_definition_id) sans filet : le laisser
|
||||
# passer plantait TOUTE la requête (500), et comme la ligne
|
||||
# _definitions n'était alors jamais validée (pas de commit()
|
||||
# atteint), l'objet n'était pas créé du tout — pas de champ
|
||||
# "Relation" non plus, mais un panneau qui semble "ne rien
|
||||
# faire" plutôt qu'un message d'erreur clair. On ignore donc ce
|
||||
# champ précis (comme une ligne sans nom, ci-dessus) plutôt que
|
||||
# de faire échouer la création de TOUT l'objet à cause de lui.
|
||||
continue
|
||||
fields.append({
|
||||
"name": fname.strip(),
|
||||
"type": field_types[i],
|
||||
"relation_definition_id": field_relations[i] if i < len(field_relations) else None,
|
||||
"type": ftype,
|
||||
"relation_definition_id": relation_definition_id,
|
||||
"required": field_required[i] == "1" if i < len(field_required) else False,
|
||||
"min_value": (field_min[i].strip() or None) if i < len(field_min) else None,
|
||||
"max_value": (field_max[i].strip() or None) if i < len(field_max) else None,
|
||||
|
||||
@@ -25,3 +25,47 @@ def test_object_not_created_without_a_name(client, game):
|
||||
resp = client.post(f"/game/{game}/objects/new", data={"object_name": " "}, follow_redirects=False)
|
||||
assert resp.status_code == 302
|
||||
assert db.list_definitions(game) == []
|
||||
|
||||
|
||||
def test_relation_field_without_a_chosen_target_does_not_crash_the_whole_request(client, game):
|
||||
"""Régression : "Relation vers un autre objet" est TOUJOURS une option
|
||||
du sélecteur de type (db/constants.py), même quand il n'existe encore
|
||||
AUCUN autre objet à pointer (ex. le tout premier objet créé dans un
|
||||
jeu) — le sélecteur "Objet lié" est alors vide, et
|
||||
create_definition() faisait int("") sans filet, plantant TOUTE la
|
||||
requête (500) avant même d'atteindre son commit() : l'objet n'était
|
||||
donc PAS créé du tout, pas seulement son champ "Relation" — vécu comme
|
||||
"le panneau recharge la page sans créer d'objet" alors que des champs
|
||||
avaient bien été renseignés. Le champ "Relation" invalide doit être
|
||||
ignoré (comme une ligne sans nom), pas faire échouer tout le reste."""
|
||||
resp = client.post(f"/game/{game}/objects/new", data={
|
||||
"object_name": "Personnage",
|
||||
"field_name[]": ["nom", "arme"],
|
||||
"field_type[]": ["texte", "relation"],
|
||||
"field_relation[]": ["", ""],
|
||||
"field_required[]": ["0", "0"],
|
||||
"field_min[]": ["", ""],
|
||||
"field_max[]": ["", ""],
|
||||
}, follow_redirects=False)
|
||||
assert resp.status_code == 302
|
||||
definitions = db.list_definitions(game)
|
||||
assert len(definitions) == 1
|
||||
fields = db.get_definition(game, definitions[0]["id"])["fields"]
|
||||
assert [f["name"] for f in fields] == ["nom"]
|
||||
|
||||
|
||||
def test_add_relation_field_without_a_chosen_target_is_ignored_not_a_crash(client, game):
|
||||
"""Même garde, côté "+ Ajouter un champ" du panneau d'un objet déjà
|
||||
créé (object_field_add.py / add_field_to_definition.py)."""
|
||||
resp = client.post(f"/game/{game}/objects/new", data={
|
||||
"object_name": "Personnage", "field_name[]": ["nom"], "field_type[]": ["texte"],
|
||||
"field_relation[]": [""], "field_required[]": ["0"], "field_min[]": [""], "field_max[]": [""],
|
||||
}, follow_redirects=False)
|
||||
def_id = int(resp.headers["Location"].rstrip("/").split("/")[-1])
|
||||
|
||||
resp = client.post(f"/game/{game}/objects/{def_id}/fields/add", data={
|
||||
"field_name": "arme", "field_type": "relation", "field_relation": "",
|
||||
}, follow_redirects=False)
|
||||
assert resp.status_code == 302
|
||||
fields = db.get_definition(game, def_id)["fields"]
|
||||
assert [f["name"] for f in fields] == ["nom"]
|
||||
|
||||
Reference in New Issue
Block a user