diff --git a/src/servala/core/crd.py b/src/servala/core/crd.py index 8c31a27..e414168 100644 --- a/src/servala/core/crd.py +++ b/src/servala/core/crd.py @@ -6,7 +6,7 @@ from django.db import models from django.forms.models import ModelForm, ModelFormMetaclass from django.utils.translation import gettext_lazy as _ -from servala.core.models import ControlPlaneCRD, ServiceInstance +from servala.core.models import ServiceInstance, ControlPlaneCRD from servala.frontend.forms.widgets import DynamicArrayField, DynamicArrayWidget diff --git a/src/servala/core/forms.py b/src/servala/core/forms.py index 36f8be5..9742233 100644 --- a/src/servala/core/forms.py +++ b/src/servala/core/forms.py @@ -178,80 +178,8 @@ class ServiceDefinitionAdminForm(forms.ModelForm): {"form_config": _("Schema error: {}").format(e.message)} ) - self._validate_field_mappings(form_config, cleaned_data) - return cleaned_data - def _validate_field_mappings(self, form_config, cleaned_data): - if not self.instance.pk: - return - crd = self.instance.offering_control_planes.all().first() - if not crd: - return - - schema = None - try: - schema = crd.resource_schema - except Exception as e: - pass - - if not schema or not (spec_schema := schema.get("properties", {}).get("spec")): - return - - valid_paths = self._extract_field_paths(spec_schema, "spec") | {"name"} - included_mappings = set() - errors = [] - for fieldset in form_config.get("fieldsets", []): - for field in fieldset.get("fields", []): - mapping = field.get("controlplane_field_mapping") - included_mappings.add(mapping) - if mapping and mapping not in valid_paths: - field_name = field.get("name", mapping) - errors.append( - _( - "Field '{}' has invalid mapping '{}'. Valid paths are: {}" - ).format( - field_name, - mapping, - ", ".join(sorted(valid_paths)[:10]) - + ("..." if len(valid_paths) > 10 else ""), - ) - ) - - if "name" not in included_mappings: - raise forms.ValidationError( - { - "form_config": _( - "You must include a `name` field in the custom form config." - ) - } - ) - - if errors: - raise forms.ValidationError({"form_config": errors}) - - def _extract_field_paths(self, schema, prefix=""): - paths = set() - - if not isinstance(schema, dict): - return paths - - if "type" in schema and schema["type"] != "object": - if prefix: - paths.add(prefix) - - if schema.get("properties"): - for prop_name, prop_schema in schema["properties"].items(): - new_prefix = f"{prefix}.{prop_name}" if prefix else prop_name - paths.add(new_prefix) - paths.update(self._extract_field_paths(prop_schema, new_prefix)) - - if schema.get("type") == "array" and "items" in schema: - if prefix: - paths.add(prefix) - - return paths - def save(self, *args, **kwargs): self.instance.api_definition = self.cleaned_data["api_definition"] return super().save(*args, **kwargs) diff --git a/src/servala/frontend/forms/organization.py b/src/servala/frontend/forms/organization.py index 45e7b11..86ba0ab 100644 --- a/src/servala/frontend/forms/organization.py +++ b/src/servala/frontend/forms/organization.py @@ -8,6 +8,7 @@ from servala.core.models import Organization, OrganizationInvitation, Organizati from servala.core.odoo import get_invoice_addresses, get_odoo_countries from servala.frontend.forms.mixins import HtmxMixin + ORG_NAME_PATTERN = r"[\w\s\-.,&'()+]+" diff --git a/src/servala/frontend/views/service.py b/src/servala/frontend/views/service.py index 0f9800e..2c6923e 100644 --- a/src/servala/frontend/views/service.py +++ b/src/servala/frontend/views/service.py @@ -123,9 +123,7 @@ class ServiceOfferingDetailView(OrganizationViewMixin, HtmxViewMixin, DetailView def context_object(self): if self.request.method == "POST": return ControlPlaneCRD.objects.filter( - pk=self.request.POST.get( - "expert-context", self.request.POST.get("custom-context") - ), + pk=self.request.POST.get("expert-context", self.request.POST.get("custom-context")), # Make sure we don’t use a malicious ID control_plane__in=self.planes, ).first() @@ -133,27 +131,19 @@ class ServiceOfferingDetailView(OrganizationViewMixin, HtmxViewMixin, DetailView control_plane=self.selected_plane, service_offering=self.object ).first() + def get_instance_form_kwargs(self, ignore_data=False): - return { - "initial": { - "organization": self.request.organization, - "context": self.context_object, - }, - "prefix": "expert", - "data": ( - self.request.POST - if (self.request.method == "POST" and not ignore_data) - else None - ), - } + return {"initial": { + "organization": self.request.organization, + "context": self.context_object, + }, "prefix": "expert", "data": self.request.POST if (self.request.method == "POST" and not ignore_data) else None + } def get_instance_form(self, ignore_data=False): if not self.context_object or not self.context_object.model_form_class: return - return self.context_object.model_form_class( - **self.get_instance_form_kwargs(ignore_data=ignore_data) - ) + return self.context_object.model_form_class(**self.get_instance_form_kwargs(ignore_data=ignore_data)) def get_custom_instance_form(self, ignore_data=False): if not self.context_object or not self.context_object.custom_model_form_class: @@ -179,9 +169,7 @@ class ServiceOfferingDetailView(OrganizationViewMixin, HtmxViewMixin, DetailView context["custom_service_form"] = self.get_custom_instance_form() else: context["service_form"] = self.get_instance_form() - context["custom_service_form"] = self.get_custom_instance_form( - ignore_data=True - ) + context["custom_service_form"] = self.get_custom_instance_form(ignore_data=True) else: context["service_form"] = self.get_instance_form() context["custom_service_form"] = self.get_custom_instance_form()