import json # Partagé par control_value.py (widgets) et filter_repeater_rows.py # (rendering) — module "feuille" sans import de l'un ou l'autre, pour ne # jamais introduire de dépendance circulaire entre ces deux packages. def decode_clauses(attrs, clauses_attr, combinator_attr, legacy_prefix=None): """Lit une liste de clauses {"champ", "operateur", "valeur"} depuis un attribut JSON (clauses_attr). Repli sur l'ANCIEN format à 2 filtres fixes (legacy_prefix + "_champ"/"_operateur"/"_valeur", et legacy_prefix + "2_champ"/"2_operateur"/"2_valeur") si clauses_attr est absent — pour ne rien casser sur les éléments créés avant l'ajout des clauses illimitées (aucune migration de données nécessaire, la conversion se fait à la volée, ici, à la lecture). Renvoie toujours (clauses, combinateur), combinateur valant "et" par défaut.""" raw = attrs.get(clauses_attr) clauses = None if raw: try: parsed = json.loads(raw) if isinstance(parsed, list): clauses = parsed except (TypeError, ValueError): clauses = None if clauses is None and legacy_prefix: clauses = [] for suffix in ("", "2"): champ = (attrs.get(f"{legacy_prefix}{suffix}_champ") or "").strip() if champ: clauses.append({ "champ": champ, "operateur": attrs.get(f"{legacy_prefix}{suffix}_operateur") or "egal", "valeur": attrs.get(f"{legacy_prefix}{suffix}_valeur") or "", }) combinateur = attrs.get(combinator_attr) or "et" return clauses or [], combinateur def encode_clauses(clauses): """Sérialise une liste de clauses en JSON, en retirant celles sans nom de champ (une ligne ajoutée puis jamais remplie) — "" si la liste nettoyée est vide, pour ne rien écrire de superflu en base.""" cleaned = [c for c in clauses if (c.get("champ") or "").strip()] return json.dumps(cleaned) if cleaned else ""