diff --git a/src/servala/core/admin.py b/src/servala/core/admin.py index 39fbab9..60fe147 100644 --- a/src/servala/core/admin.py +++ b/src/servala/core/admin.py @@ -322,7 +322,7 @@ class ServiceDefinitionAdmin(admin.ModelAdmin): ( _("Form Configuration"), { - "fields": ("form_config",), + "fields": ("form_config", "hide_expert_mode"), "description": _( "Optional custom form configuration. When provided, this will be used instead of auto-generating the form from the OpenAPI spec." ), diff --git a/src/servala/core/migrations/0014_hide_billing_address.py b/src/servala/core/migrations/0014_hide_billing_address.py index 7295f3a..1e7f3bf 100644 --- a/src/servala/core/migrations/0014_hide_billing_address.py +++ b/src/servala/core/migrations/0014_hide_billing_address.py @@ -33,7 +33,10 @@ class Migration(migrations.Migration): name="user_info", field=models.JSONField( blank=True, - help_text='Array of info objects: [{"title": "…", "content": "…", "help_text": "…"}]. The help_text field is optional and will be shown as a hover popover on an info icon.', + help_text=( + 'Array of info objects: [{"title": "…", "content": "…", "help_text": "…"}]. ' + "The help_text field is optional and will be shown as a hover popover on an info icon." + ), null=True, verbose_name="User Information", ), diff --git a/src/servala/core/migrations/0015_add_hide_expert_mode_to_service_definition.py b/src/servala/core/migrations/0015_add_hide_expert_mode_to_service_definition.py new file mode 100644 index 0000000..cc88c9d --- /dev/null +++ b/src/servala/core/migrations/0015_add_hide_expert_mode_to_service_definition.py @@ -0,0 +1,25 @@ +# Generated by Django 5.2.8 on 2025-11-14 15:55 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("core", "0014_hide_billing_address"), + ] + + operations = [ + migrations.AddField( + model_name="servicedefinition", + name="hide_expert_mode", + field=models.BooleanField( + default=False, + help_text=( + "When enabled, the 'Show Expert Mode' toggle will be hidden and only the custom form configuration will be available. " + "Only applies when a custom form configuration is provided." + ), + verbose_name="Disable Expert Mode", + ), + ), + ] diff --git a/src/servala/core/models/service.py b/src/servala/core/models/service.py index 29bae9f..ab7b76f 100644 --- a/src/servala/core/models/service.py +++ b/src/servala/core/models/service.py @@ -370,6 +370,14 @@ class ServiceDefinition(ServalaModelMixin, models.Model): null=True, blank=True, ) + hide_expert_mode = models.BooleanField( + default=False, + verbose_name=_("Disable Expert Mode"), + help_text=_( + "When enabled, the 'Show Expert Mode' toggle will be hidden and only the custom form " + "configuration will be available. Only applies when a custom form configuration is provided." + ), + ) service = models.ForeignKey( to="Service", on_delete=models.CASCADE, diff --git a/src/servala/frontend/templates/frontend/organizations/service_instance_update.html b/src/servala/frontend/templates/frontend/organizations/service_instance_update.html index 74259e6..17b9a51 100644 --- a/src/servala/frontend/templates/frontend/organizations/service_instance_update.html +++ b/src/servala/frontend/templates/frontend/organizations/service_instance_update.html @@ -13,7 +13,7 @@ {% translate "Back" %} {% endblock page_title_extra %} {% partialdef service-form %} -{% if form %} +{% if form or custom_form %}
@@ -31,7 +31,7 @@ {% block content %}
- {% if not form %} + {% if not form and not custom_form %} diff --git a/src/servala/frontend/templates/includes/tabbed_fieldset_form.html b/src/servala/frontend/templates/includes/tabbed_fieldset_form.html index 009ad94..5f289b7 100644 --- a/src/servala/frontend/templates/includes/tabbed_fieldset_form.html +++ b/src/servala/frontend/templates/includes/tabbed_fieldset_form.html @@ -6,7 +6,7 @@ {% if form_action %}action="{{ form_action }}"{% endif %}> {% csrf_token %} {% include "frontend/forms/errors.html" %} - {% if form %} + {% if form and expert_form and not hide_expert_mode %} - {% if expert_form %} + {% if expert_form and not hide_expert_mode %}
@@ -144,6 +144,6 @@
-{% if form %} +{% if form and not hide_expert_mode %} {% endif %} diff --git a/src/servala/frontend/views/service.py b/src/servala/frontend/views/service.py index 1aeac59..c26194d 100644 --- a/src/servala/frontend/views/service.py +++ b/src/servala/frontend/views/service.py @@ -167,7 +167,11 @@ class ServiceOfferingDetailView(OrganizationViewMixin, HtmxViewMixin, DetailView } def get_instance_form(self, ignore_data=False): - if not self.context_object or not self.context_object.model_form_class: + if ( + not self.context_object + or not self.context_object.model_form_class + or self.hide_expert_mode + ): return return self.context_object.model_form_class( @@ -187,11 +191,21 @@ class ServiceOfferingDetailView(OrganizationViewMixin, HtmxViewMixin, DetailView # vs "expert form" = auto-generated (all technical fields) return self.request.POST.get("active_form", "expert") == "custom" + @cached_property + def hide_expert_mode(self): + return ( + self.context_object + and self.context_object.service_definition + and self.context_object.service_definition.form_config + and self.context_object.service_definition.hide_expert_mode + ) + def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) context["select_form"] = self.select_form context["has_control_planes"] = self.planes.exists() context["selected_plane"] = self.selected_plane + context["hide_expert_mode"] = self.hide_expert_mode if self.request.method == "POST": if self.is_custom_form: context["service_form"] = self.get_instance_form(ignore_data=True) @@ -441,6 +455,8 @@ class ServiceInstanceUpdateView( return kwargs def get_form(self, *args, ignore_data=False, **kwargs): + if self.hide_expert_mode: + return if not ignore_data: return super().get_form(*args, **kwargs) cls = self.get_form_class() @@ -477,8 +493,19 @@ class ServiceInstanceUpdateView( return self.form_valid(form) return self.form_invalid(form) + @cached_property + def hide_expert_mode(self): + return ( + self.object + and self.object.context + and self.object.context.service_definition + and self.object.context.service_definition.form_config + and self.object.context.service_definition.hide_expert_mode + ) + def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) + context["hide_expert_mode"] = self.hide_expert_mode if self.request.method == "POST": if self.is_custom_form: context["custom_form"] = self.get_custom_form() diff --git a/src/tests/test_views.py b/src/tests/test_views.py index a97ecd1..0a39444 100644 --- a/src/tests/test_views.py +++ b/src/tests/test_views.py @@ -2,6 +2,7 @@ import pytest from servala.core.models.service import CloudProvider, ServiceOffering + @pytest.mark.parametrize( "url,redirect", (