Custom form configuration #268
2 changed files with 155 additions and 0 deletions
|
|
@ -161,6 +161,9 @@ class ServiceDefinitionAdminForm(forms.ModelForm):
|
|||
|
||||
form_config = cleaned_data.get("form_config")
|
||||
if form_config:
|
||||
form_config = self._normalize_form_config_types(form_config)
|
||||
cleaned_data["form_config"] = form_config
|
||||
|
||||
try:
|
||||
jsonschema.validate(
|
||||
instance=form_config, schema=self.form_config_schema
|
||||
|
|
@ -182,6 +185,42 @@ class ServiceDefinitionAdminForm(forms.ModelForm):
|
|||
|
||||
return cleaned_data
|
||||
|
||||
def _normalize_form_config_types(self, form_config):
|
||||
"""
|
||||
Normalize form_config by converting string representations of numbers
|
||||
to actual integers/floats. The JSON form widget sends all values
|
||||
as strings, but the schema expects proper types.
|
||||
"""
|
||||
if not isinstance(form_config, dict):
|
||||
return form_config
|
||||
|
||||
integer_fields = ["max_length", "rows", "min_values", "max_values"]
|
||||
number_fields = ["min_value", "max_value"]
|
||||
|
||||
for fieldset in form_config.get("fieldsets", []):
|
||||
for field in fieldset.get("fields", []):
|
||||
for field_name in integer_fields:
|
||||
if field_name in field and field[field_name] is not None:
|
||||
value = field[field_name]
|
||||
if isinstance(value, str):
|
||||
try:
|
||||
field[field_name] = int(value) if value else None
|
||||
except (ValueError, TypeError):
|
||||
pass
|
||||
|
||||
for field_name in number_fields:
|
||||
if field_name in field and field[field_name] is not None:
|
||||
value = field[field_name]
|
||||
if isinstance(value, str):
|
||||
try:
|
||||
field[field_name] = (
|
||||
int(value) if "." not in value else float(value)
|
||||
)
|
||||
except (ValueError, TypeError):
|
||||
pass
|
||||
|
||||
return form_config
|
||||
|
||||
def _validate_field_mappings(self, form_config, cleaned_data):
|
||||
if not self.instance.pk:
|
||||
return
|
||||
|
|
|
|||
|
|
@ -633,3 +633,119 @@ def test_default_value_not_override_existing_instance():
|
|||
|
||||
assert form.initial["name"] == "existing-name"
|
||||
assert form.initial["port"] == 3000
|
||||
|
||||
|
||||
def test_form_config_coerces_string_numbers_to_integers():
|
||||
form = ServiceDefinitionAdminForm()
|
||||
schema = form.form_config_schema
|
||||
|
||||
config_with_string_numbers = {
|
||||
"fieldsets": [
|
||||
{
|
||||
"fields": [
|
||||
{
|
||||
"type": "text",
|
||||
"label": "Service Name",
|
||||
"controlplane_field_mapping": "spec.serviceName",
|
||||
"max_length": "64", # String instead of integer
|
||||
"required": True,
|
||||
},
|
||||
{
|
||||
"type": "textarea",
|
||||
"label": "Description",
|
||||
"controlplane_field_mapping": "spec.description",
|
||||
"rows": "5", # String instead of integer
|
||||
"max_length": "500", # String instead of integer
|
||||
},
|
||||
{
|
||||
"type": "number",
|
||||
"label": "Port",
|
||||
"controlplane_field_mapping": "spec.port",
|
||||
"min_value": "1", # String instead of integer
|
||||
"max_value": "65535", # String instead of integer
|
||||
},
|
||||
{
|
||||
"type": "array",
|
||||
"label": "Tags",
|
||||
"controlplane_field_mapping": "spec.tags",
|
||||
"min_values": "0", # String instead of integer
|
||||
"max_values": "10", # String instead of integer
|
||||
},
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
normalized_config = form._normalize_form_config_types(config_with_string_numbers)
|
||||
fields = normalized_config["fieldsets"][0]["fields"]
|
||||
|
||||
assert fields[0]["max_length"] == 64
|
||||
assert isinstance(fields[0]["max_length"], int)
|
||||
|
||||
assert fields[1]["rows"] == 5
|
||||
assert isinstance(fields[1]["rows"], int)
|
||||
assert fields[1]["max_length"] == 500
|
||||
assert isinstance(fields[1]["max_length"], int)
|
||||
|
||||
assert fields[2]["min_value"] == 1
|
||||
assert isinstance(fields[2]["min_value"], int)
|
||||
assert fields[2]["max_value"] == 65535
|
||||
assert isinstance(fields[2]["max_value"], int)
|
||||
|
||||
assert fields[3]["min_values"] == 0
|
||||
assert isinstance(fields[3]["min_values"], int)
|
||||
assert fields[3]["max_values"] == 10
|
||||
assert isinstance(fields[3]["max_values"], int)
|
||||
|
||||
jsonschema.validate(instance=normalized_config, schema=schema)
|
||||
|
||||
|
||||
def test_form_config_handles_float_numbers():
|
||||
form = ServiceDefinitionAdminForm()
|
||||
|
||||
config_with_floats = {
|
||||
"fieldsets": [
|
||||
{
|
||||
"fields": [
|
||||
{
|
||||
"type": "number",
|
||||
"label": "Price",
|
||||
"controlplane_field_mapping": "spec.price",
|
||||
"min_value": "0.01", # String float
|
||||
"max_value": "999.99", # String float
|
||||
},
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
normalized_config = form._normalize_form_config_types(config_with_floats)
|
||||
field = normalized_config["fieldsets"][0]["fields"][0]
|
||||
|
||||
assert field["min_value"] == 0.01
|
||||
assert isinstance(field["min_value"], float)
|
||||
assert field["max_value"] == 999.99
|
||||
assert isinstance(field["max_value"], float)
|
||||
|
||||
|
||||
def test_form_config_handles_empty_string_as_none():
|
||||
form = ServiceDefinitionAdminForm()
|
||||
|
||||
config_with_empty_strings = {
|
||||
"fieldsets": [
|
||||
{
|
||||
"fields": [
|
||||
{
|
||||
"type": "text",
|
||||
"label": "Name",
|
||||
"controlplane_field_mapping": "name",
|
||||
"max_length": "", # Empty string
|
||||
},
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
normalized_config = form._normalize_form_config_types(config_with_empty_strings)
|
||||
field = normalized_config["fieldsets"][0]["fields"][0]
|
||||
assert field["max_length"] is None
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue