Le tour précédent exigeait que CHAQUE widget règle sa propre "Donnée
liée" (_data_definition_id) pour voir apparaître le sélecteur de champ -
mais un élément de jeu ("Mail card"...) créé avec un "Objet lié" (voir
element_types.html, bound_definition_id) a précisément pour but d'éviter
ce réglage widget par widget : ses {{champ}} sont censés venir de CET
objet, fourni plus tard par le Répéteur qui l'utilisera comme modèle de
ligne. D'où le bug remonté : un nouveau Titre/Texte posé dans un tel
élément de jeu n'affichait jamais le sélecteur.
Ajoute get_element_type_by_template_screen(slug, screen_id), pour
retrouver depuis l'éditeur d'un écran-modèle l'entrée du catalogue (et
donc l'objet lié) dont il est la recette. screen_edit.py le calcule pour
le panneau de propriétés et le passe à controls_with_values(), qui
l'utilise comme repli pour le champ "Contenu" SEULEMENT si ce widget n'a
pas déjà sa propre "Donnée liée" réglée (priorité conservée au réglage le
plus spécifique).
Ajoute tests/test_element_type_bound_field_picker.py (apparition sans
réglage supplémentaire, absence sans objet lié, priorité à la "Donnée
liée" du widget si réglée).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
96 lines
6.4 KiB
Python
96 lines
6.4 KiB
Python
import db
|
|
|
|
from .widget_meta import widget_meta
|
|
from .control_value import _control_value
|
|
from .data_definition_options import data_definition_options
|
|
from ..element_types.list_element_types import list_element_types
|
|
|
|
|
|
def _field_label(f):
|
|
"""Libellé affiché dans une liste déroulante de champs — précise l'objet
|
|
visé pour un champ "relation" (ex. "parcour (→ parcours)"), pour qu'on
|
|
sache ce qu'on choisit sans avoir à aller vérifier dans l'objet."""
|
|
if f["type"] == "relation" and f.get("relation_definition_name"):
|
|
return f"{f['name']} (→ {f['relation_definition_name']})"
|
|
return f["name"]
|
|
|
|
|
|
def controls_with_values(slug, el, template_definition_id=None):
|
|
"""Pour le panneau de propriétés : la liste des réglages du widget de
|
|
cet élément, chacun avec sa valeur actuelle déjà résolue. Le réglage
|
|
"définition_id" du Répéteur de données (et de la Jauge) a besoin du jeu
|
|
courant pour lister les objets disponibles — d'où le paramètre slug.
|
|
|
|
`template_definition_id` : l'objet lié à l'ÉLÉMENT DE JEU en cours
|
|
d'édition (bound_definition_id, réglé à sa création — voir
|
|
element_types.html), passé par screen_edit.py seulement quand on
|
|
édite un écran-modèle qui en a un. Sert de repli pour le champ
|
|
"Contenu" (voir plus bas) : si CE widget n'a pas sa propre "Donnée
|
|
liée" réglée, ses {{champ}} font quand même référence à l'objet lié
|
|
de l'élément de jeu (c'est lui qui fournira les valeurs une fois posé
|
|
dans un Répéteur), donc le sélecteur de champ doit lister SES champs
|
|
par défaut plutôt que de rester vide."""
|
|
meta = widget_meta(el.get("widget"))
|
|
attributes = el.get("attributes") or {}
|
|
# Pour la Jauge : quel enregistrement suivre (row_id) et quels champs
|
|
# afficher (champ/champ_nom) ne peuvent être proposés qu'une fois un
|
|
# objet choisi (definition_id, déjà sur CET élément) — voir
|
|
# render_jauge.py. La mise à jour EN DIRECT quand on change d'objet
|
|
# sans quitter le panneau est gérée côté client (onJaugeDefinitionChange
|
|
# dans screen_edit.html), ceci ne couvre que le premier affichage/une
|
|
# navigation vers un élément déjà réglé.
|
|
jauge_options = data_definition_options(slug, attributes.get("_definition_id"))
|
|
# Le Texte/Titre lié à une donnée (voir DATA_BINDING_CONTROLS) garde SON
|
|
# objet dans un attribut séparé (_data_definition_id) — jamais le même
|
|
# élément que le Répéteur/la Jauge, donc jamais besoin des deux options
|
|
# en même temps, mais un calcul dédié quand même pour rester correct si
|
|
# ça change un jour.
|
|
data_binding_options = data_definition_options(slug, attributes.get("_data_definition_id"))
|
|
# Repli pour "content" (voir plus bas) quand ce widget n'a pas sa
|
|
# propre "Donnée liée" : les champs de l'objet lié à l'élément de jeu.
|
|
template_bound_options = data_definition_options(slug, template_definition_id) if template_definition_id else None
|
|
# Condition de visibilité (voir VISIBILITY_CONDITION_CONTROLS) : elle
|
|
# aussi garde SON objet dans un attribut séparé (_visibilite_cond_
|
|
# definition_id), jamais partagé avec le Répéteur/la Jauge/Donnée liée.
|
|
visibility_cond_options = data_definition_options(slug, attributes.get("_visibilite_cond_definition_id"))
|
|
global_variables = db.list_global_variables(slug)
|
|
result = []
|
|
for control in meta.get("controls", []):
|
|
control = dict(control)
|
|
if control["key"] in ("definition_id", "data_definition_id", "visibilite_cond_definition_id"):
|
|
defs = db.list_definitions(slug)
|
|
control["options"] = [("", "(sélectionner un objet)")] + [(str(d["id"]), d["name"]) for d in defs]
|
|
if control["key"] == "visibilite_cond_champ":
|
|
control["options"] = [("", "(sélectionner un objet d'abord)")] + [(f["name"], _field_label(f)) for f in visibility_cond_options["fields"]]
|
|
if control["key"] == "visibilite_cond_variable":
|
|
control["options"] = [("", "(sélectionner une variable)")] + [(v["name"], v["name"]) for v in global_variables]
|
|
if control["key"] == "template_type_id":
|
|
types = list_element_types(slug)
|
|
control["options"] = [("", "(aucun — utiliser le modèle de texte ci-dessous)")] + [(str(t["id"]), t["name"]) for t in types]
|
|
if control["key"] == "row_id":
|
|
control["options"] = [("", "(sélectionner un objet d'abord)")] + [(str(r["id"]), r["label"]) for r in jauge_options["rows"]]
|
|
if control["key"] in ("champ", "champ_nom"):
|
|
# La Jauge affiche une VALEUR (champ) ou un NOM (champ_nom) tel
|
|
# quel — un champ "relation" n'y a pas de sens (c'est un id, pas
|
|
# une valeur à afficher), donc exclu ici uniquement.
|
|
non_relation_fields = [f for f in jauge_options["fields"] if f["type"] != "relation"]
|
|
empty_label = "— aucun —" if control["key"] == "champ_nom" else "(sélectionner un objet d'abord)"
|
|
control["options"] = [("", empty_label)] + [(f["name"], _field_label(f)) for f in non_relation_fields]
|
|
if control["key"] in ("filtre_champ", "filtre2_champ"):
|
|
control["options"] = [("", "(sélectionner un objet d'abord)")] + [(f["name"], _field_label(f)) for f in jauge_options["fields"]]
|
|
if control["key"] in ("data_filtre_champ", "data_filtre2_champ"):
|
|
control["options"] = [("", "(sélectionner un objet d'abord)")] + [(f["name"], _field_label(f)) for f in data_binding_options["fields"]]
|
|
if control["key"] == "content":
|
|
# Une fois "Lier à un objet de données" réglé (voir
|
|
# DATA_BINDING_CONTROLS), propose les champs de CET objet en
|
|
# liste déroulante à côté du texte — plutôt que de devoir taper
|
|
# "{{nom_du_champ}}" à la main sans savoir quels noms existent
|
|
# (voir insertFieldAtCursor() dans screen_edit.html). À défaut,
|
|
# retombe sur l'objet lié à l'élément de jeu (voir docstring).
|
|
if attributes.get("_data_definition_id"):
|
|
control["field_options"] = [(f["name"], _field_label(f)) for f in data_binding_options["fields"]]
|
|
elif template_bound_options:
|
|
control["field_options"] = [(f["name"], _field_label(f)) for f in template_bound_options["fields"]]
|
|
result.append({**control, "value": _control_value(el, control)})
|
|
return result
|