feat(templates): refonte complète des templates façon Notion + vues/agents-skills
Templates (v7.71.x) : - registre unifié \ emplates\ (migrations 48-49) + TemplateService.instantiate unique (UI, API v2, agent, scheduler) - sélecteur (pilule page vide, menu •••, commande /template), gestionnaire /templates, menu New ▾, From template, base inline dans un document - 141 presets système (59 pages, 42 bases, 15 blocs, 25 lignes), titre auto depuis le template, variables title réservée - récurrences RRULE + scheduler dédupliqué, agent apply_template/list_templates, API /api/templates + /api/v2/fd-templates - correctifs : bouton Templates, centrage fenêtre, filtres CSP, flux de création, variable title - tests : tests/test_fd_templates.py (19) et e2e/templates_picker.spec.js (8) Inclut le travail déjà présent dans le working tree (vues Notion : view_query/view_aggregate/form_projection/geocoding, property_types, database_table, docs agents-skills) et ignore .playwright-mcp/.
This commit is contained in:
@@ -0,0 +1,68 @@
|
||||
"""FlowDeck — Projection Form builder -> pipeline public /f/ (§16.5).
|
||||
|
||||
Le builder ecrit ``form_questions`` + reglages de la vue form ; le
|
||||
projecteur regenere ``collections.form_config_json`` (format v6.8 inchange)
|
||||
consomme par GET/POST /f/<token>. Le public ne lit jamais les tables du
|
||||
builder : zero regression possible sur les liens deja diffuses.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
|
||||
|
||||
def project_form_config(view_config: dict, questions: list[dict]) -> dict:
|
||||
"""Construit le ``form_config_json`` v6.8 depuis le builder."""
|
||||
cfg = dict(view_config or {})
|
||||
sharing = cfg.get("sharing") or {}
|
||||
submit = cfg.get("submit_screen") or {}
|
||||
fields = []
|
||||
for q in sorted(questions, key=lambda x: x.get("position", 0)):
|
||||
fields.append({
|
||||
"property": q.get("property_name"),
|
||||
"label": q.get("label_override") or q.get("property_name"),
|
||||
"required": bool(q.get("required")),
|
||||
"help": q.get("help_text") or "",
|
||||
"widget": q.get("widget") or "auto",
|
||||
"max_selections": q.get("max_selections"),
|
||||
"conditional": json.loads(q["conditional_json"]) if q.get("conditional_json") else None,
|
||||
})
|
||||
return {
|
||||
"title": cfg.get("title") or "Form",
|
||||
"description": cfg.get("description") or "",
|
||||
"fields": fields,
|
||||
"sharing": {
|
||||
"audience": sharing.get("audience", "workspace"),
|
||||
"anonymous": bool(sharing.get("anonymous", False)),
|
||||
"respondent_access": sharing.get("respondent_access", "none"),
|
||||
"branding": sharing.get("branding", True),
|
||||
},
|
||||
"submit": {
|
||||
"button_text": submit.get("button_text", "Envoyer"),
|
||||
"button_color": submit.get("button_color", "blue"),
|
||||
"confirmation_title": submit.get("confirmation_title", "Merci !"),
|
||||
"confirmation_body": submit.get("confirmation_body", "Votre réponse a été enregistrée."),
|
||||
},
|
||||
"form_view_id": cfg.get("form_view_id"),
|
||||
}
|
||||
|
||||
|
||||
def default_questions_for(properties: list[dict]) -> list[dict]:
|
||||
"""Questions initiales depuis les proprietes exposables (hors formules)."""
|
||||
skip = {"formula", "rollup", "button", "unique_id",
|
||||
"created_time", "created_by", "last_edited_time", "last_edited_by"}
|
||||
out = []
|
||||
for i, p in enumerate(properties):
|
||||
if p.get("prop_type") in skip:
|
||||
continue
|
||||
out.append({
|
||||
"property_name": p.get("name"),
|
||||
"position": i,
|
||||
"label_override": None,
|
||||
"sync_label": 1,
|
||||
"help_text": "",
|
||||
"required": 0,
|
||||
"widget": "auto",
|
||||
"max_selections": None,
|
||||
"conditional_json": None,
|
||||
})
|
||||
return out
|
||||
Reference in New Issue
Block a user