Provide form building schema

This commit is contained in:
Tobias Kunze 2025-10-31 11:49:04 +01:00
parent 880d10c5e5
commit 1cf1947539
3 changed files with 163 additions and 0 deletions

View file

@ -1,3 +1,7 @@
import json
from pathlib import Path
import jsonschema
from django import forms
from django.utils.translation import gettext_lazy as _
from django_jsonform.widgets import JSONFormWidget
@ -124,6 +128,10 @@ class ServiceDefinitionAdminForm(forms.ModelForm):
self.fields["api_version"].initial = api_def.get("version", "")
self.fields["api_kind"].initial = api_def.get("kind", "")
schema_path = Path(__file__).parent / "schemas" / "form_config_schema.json"
with open(schema_path) as f:
self.form_config_schema = json.load(f)
def clean(self):
cleaned_data = super().clean()
@ -151,6 +159,19 @@ class ServiceDefinitionAdminForm(forms.ModelForm):
api_def["kind"] = api_kind
cleaned_data["api_definition"] = api_def
form_config = cleaned_data.get("form_config")
if form_config:
try:
jsonschema.validate(instance=form_config, schema=self.form_config_schema)
except jsonschema.ValidationError as e:
raise forms.ValidationError(
{"form_config": _("Invalid form configuration: {}").format(e.message)}
)
except jsonschema.SchemaError as e:
raise forms.ValidationError(
{"form_config": _("Schema error: {}").format(e.message)}
)
return cleaned_data
def save(self, *args, **kwargs):

View file

@ -360,6 +360,16 @@ class ServiceDefinition(ServalaModelMixin, models.Model):
null=True,
blank=True,
)
form_config = models.JSONField(
verbose_name=_("Form Configuration"),
help_text=_(
"Optional custom form configuration. When provided, this configuration will be used "
"to render the service form instead of auto-generating it from the OpenAPI spec. "
"Format: {\"fieldsets\": [{\"title\": \"Section\", \"fields\": [{...}]}]}"
),
null=True,
blank=True,
)
service = models.ForeignKey(
to="Service",
on_delete=models.CASCADE,

View file

@ -0,0 +1,132 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "Service Definition Form Configuration Schema",
"description": "Schema for custom form configuration in ServiceDefinition",
"type": "object",
"required": ["fieldsets"],
"properties": {
"fieldsets": {
"type": "array",
"description": "Array of fieldset objects defining form sections",
"minItems": 1,
"items": {
"type": "object",
"required": ["fields"],
"properties": {
"title": {
"type": "string",
"description": "Optional title for the fieldset/tab"
},
"fields": {
"type": "array",
"description": "Array of field definitions in this fieldset",
"minItems": 1,
"items": {
"type": "object",
"required": ["name", "type", "label", "controlplane_field_mapping"],
"properties": {
"name": {
"type": "string",
"description": "Unique field name/identifier",
"pattern": "^[a-zA-Z_][a-zA-Z0-9_]*$"
},
"type": {
"type": "string",
"description": "Field type",
"enum": ["text", "email", "textarea", "number", "choice", "checkbox", "array"]
},
"label": {
"type": "string",
"description": "Human-readable field label"
},
"help_text": {
"type": "string",
"description": "Optional help text displayed below the field"
},
"required": {
"type": "boolean",
"description": "Whether the field is required",
"default": false
},
"default": {
"description": "Default value for the field"
},
"controlplane_field_mapping": {
"type": "string",
"description": "Dot-notation path mapping to Kubernetes spec field (e.g., 'spec.parameters.service.fqdn')"
},
"max_length": {
"type": "integer",
"description": "Maximum length for text/textarea fields",
"minimum": 1
},
"rows": {
"type": "integer",
"description": "Number of rows for textarea fields",
"minimum": 1
},
"min_value": {
"type": "number",
"description": "Minimum value for number fields"
},
"max_value": {
"type": "number",
"description": "Maximum value for number fields"
},
"choices": {
"type": "array",
"description": "Array of [value, label] pairs for choice fields",
"items": {
"type": "array",
"minItems": 2,
"maxItems": 2,
"items": [
{"type": "string"},
{"type": "string"}
]
}
},
"min_values": {
"type": "integer",
"description": "Minimum number of values for array fields",
"minimum": 0
},
"max_values": {
"type": "integer",
"description": "Maximum number of values for array fields",
"minimum": 1
},
"validators": {
"type": "array",
"description": "Array of validator names (for future use)",
"items": {
"type": "string",
"enum": ["email", "fqdn", "url", "ipv4", "ipv6"]
}
},
"generators": {
"type": "array",
"description": "Array of generator function names (for future use)",
"items": {
"type": "string",
"enum": ["suggest_fqdn_from_name"]
}
}
},
"allOf": [
{
"if": {
"properties": {"type": {"const": "choice"}}
},
"then": {
"required": ["choices"]
}
}
]
}
}
}
}
}
}
}