From 1a2a75880d038b514687fdfdaee1a336c72132da Mon Sep 17 00:00:00 2001 From: Tobias Kunze Date: Thu, 11 Sep 2025 14:58:08 +0200 Subject: [PATCH] Fix JSON re-parsing in re-rendered invalid form --- src/servala/frontend/forms/widgets.py | 49 ++++++++++++++++++++------ src/servala/static/js/dynamic-array.js | 5 ++- 2 files changed, 42 insertions(+), 12 deletions(-) diff --git a/src/servala/frontend/forms/widgets.py b/src/servala/frontend/forms/widgets.py index 16e19bf..d67030f 100644 --- a/src/servala/frontend/forms/widgets.py +++ b/src/servala/frontend/forms/widgets.py @@ -48,18 +48,31 @@ class DynamicArrayWidget(forms.Widget): def value_from_datadict(self, data, files, name): value = data.get(name) - if value: + if value is None: + return [] + if value == "": + return [] + + if isinstance(value, list): + return [item for item in value if item is not None and str(item).strip()] + + if isinstance(value, str): + if value.strip() == "" or value.strip().lower() == "null": + return [] try: parsed = json.loads(value) - if parsed: + if parsed is None: + return [] + if isinstance(parsed, list): return [ item for item in parsed if item is not None and str(item).strip() ] - return [] + return [parsed] if str(parsed).strip() else [] except (json.JSONDecodeError, TypeError): - pass + return [value] if value.strip() else [] + return [] @@ -104,11 +117,17 @@ class DynamicArrayField(forms.JSONField): if value is None: return [] if isinstance(value, list): - return value + return [item for item in value if item is not None] if isinstance(value, str): + if value.strip() == "" or value.strip().lower() == "null": + return [] try: parsed = json.loads(value) - return parsed if isinstance(parsed, list) else [parsed] + if parsed is None: + return [] + if isinstance(parsed, list): + return [item for item in parsed if item is not None] + return [parsed] except (json.JSONDecodeError, TypeError): return [value] if value else [] return [str(value)] @@ -117,16 +136,24 @@ class DynamicArrayField(forms.JSONField): """Handle bound data properly to avoid JSON parsing issues""" if data is None: return initial - # If data is already a list (from our widget), return it as-is if isinstance(data, list): - return data - # If data is a string, try to parse it as JSON + return [item for item in data if item is not None and str(item).strip()] if isinstance(data, str): + if data.strip() == "" or data.strip().lower() == "null": + return [] try: parsed = json.loads(data) - return parsed if isinstance(parsed, list) else [parsed] + if parsed is None: + return [] + if isinstance(parsed, list): + return [ + item + for item in parsed + if item is not None and str(item).strip() + ] + return [parsed] if str(parsed).strip() else [] except (json.JSONDecodeError, TypeError): - return [data] if data else [] + return [data] if data and data.strip() else [] return data def validate(self, value): diff --git a/src/servala/static/js/dynamic-array.js b/src/servala/static/js/dynamic-array.js index d038fdc..c198ddf 100644 --- a/src/servala/static/js/dynamic-array.js +++ b/src/servala/static/js/dynamic-array.js @@ -19,6 +19,9 @@ const initDynamicArrayWidget = () => { addRemoveEventListeners(container) addInputEventListeners(container) updateRemoveButtonVisibility(container) + + // Ensure hidden input is synced with visible inputs on initialization + updateHiddenInput(container) }) } @@ -94,7 +97,7 @@ const updateHiddenInput = (container) => { if (!hiddenInput) return const values = Array.from(inputs) - .map(input => input.value.trim()) + .map(input => input.value ? input.value.trim() : '') .filter(value => value !== '') hiddenInput.value = JSON.stringify(values)