Implement instance creation from custom form
All checks were successful
Tests / test (push) Successful in 28s
All checks were successful
Tests / test (push) Successful in 28s
This commit is contained in:
parent
7f99c78084
commit
aa77a10de2
5 changed files with 47 additions and 36 deletions
|
|
@ -292,8 +292,9 @@ class FormGeneratorMixin:
|
||||||
|
|
||||||
if "context" in self.fields:
|
if "context" in self.fields:
|
||||||
self.fields["context"].widget = forms.HiddenInput()
|
self.fields["context"].widget = forms.HiddenInput()
|
||||||
if "context" in self.initial:
|
if crd := self.initial.get("context"):
|
||||||
self.fields["context"].queryset = ControlPlaneCRD.objects.filter(pk=self.initial["context"].pk)
|
crd = getattr(crd, "pk", crd) # can be int or object
|
||||||
|
self.fields["context"].queryset = ControlPlaneCRD.objects.filter(pk=crd)
|
||||||
|
|
||||||
if self.instance and hasattr(self.instance, "name") and self.instance.name:
|
if self.instance and hasattr(self.instance, "name") and self.instance.name:
|
||||||
if "name" in self.fields:
|
if "name" in self.fields:
|
||||||
|
|
|
||||||
|
|
@ -891,7 +891,6 @@ class ServiceInstance(ServalaModelMixin, models.Model):
|
||||||
return
|
return
|
||||||
return self.context.django_model(
|
return self.context.django_model(
|
||||||
name=self.name,
|
name=self.name,
|
||||||
organization=self.organization,
|
|
||||||
context=self.context,
|
context=self.context,
|
||||||
spec=self.spec,
|
spec=self.spec,
|
||||||
# We pass -1 as ID in order to make it clear that a) this object exists (remotely),
|
# We pass -1 as ID in order to make it clear that a) this object exists (remotely),
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endpartialdef %}
|
{% endpartialdef %}
|
||||||
{% partialdef service-form %}
|
{% partialdef service-form %}
|
||||||
{% if service_form %}
|
{% if service_form or custom_service_form %}
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-header d-flex align-items-center"></div>
|
<div class="card-header d-flex align-items-center"></div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
|
|
@ -26,7 +26,7 @@
|
||||||
{% translate "Oops! Something went wrong with the service form generation. Please try again later." %}
|
{% translate "Oops! Something went wrong with the service form generation. Please try again later." %}
|
||||||
</div>
|
</div>
|
||||||
{% else %}
|
{% else %}
|
||||||
{% include "includes/tabbed_fieldset_form.html" with form=service_form %}
|
{% include "includes/tabbed_fieldset_form.html" with form=custom_service_form expert_form=service_form %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<div id="custom-form-container" class="{% if form %}custom-crd-form{% else %}expert-crd-form{% endif %}">
|
<div id="custom-form-container" class="{% if form %}custom-crd-form{% else %}expert-crd-form{% endif %}">
|
||||||
|
{% if form and form.context %}{{ form.context }}{% endif %}
|
||||||
{% if form and form.get_fieldsets|length == 1 %}
|
{% if form and form.get_fieldsets|length == 1 %}
|
||||||
{# Single fieldset - render without tabs #}
|
{# Single fieldset - render without tabs #}
|
||||||
{% for fieldset in form.get_fieldsets %}
|
{% for fieldset in form.get_fieldsets %}
|
||||||
|
|
@ -131,13 +132,10 @@
|
||||||
<input type="hidden" name="active_form" id="active-form-input" value="custom">
|
<input type="hidden" name="active_form" id="active-form-input" value="custom">
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<div class="col-sm-12 d-flex justify-content-end">
|
<div class="col-sm-12 d-flex justify-content-end">
|
||||||
<button class="btn btn-primary me-1 mb-1" type="submit">
|
<input class="btn btn-primary me-1 mb-1" type="submit"
|
||||||
{% if form_submit_label %}
|
{% if form and expert_form %}formnovalidate {% endif %} {# browser form validation fails when there are fields missing/invalid that are hidden #}
|
||||||
{{ form_submit_label }}
|
value="{% if form_submit_label %}{{ form_submit_label }}{% else %}{% translate "Save" %}{% endif %}"
|
||||||
{% else %}
|
>
|
||||||
{% translate "Save" %}
|
|
||||||
{% endif %}
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
{% if form %}
|
{% if form %}
|
||||||
|
|
|
||||||
|
|
@ -123,7 +123,7 @@ class ServiceOfferingDetailView(OrganizationViewMixin, HtmxViewMixin, DetailView
|
||||||
def context_object(self):
|
def context_object(self):
|
||||||
if self.request.method == "POST":
|
if self.request.method == "POST":
|
||||||
return ControlPlaneCRD.objects.filter(
|
return ControlPlaneCRD.objects.filter(
|
||||||
pk=self.request.POST.get("context"),
|
pk=self.request.POST.get("expert-context", self.request.POST.get("custom-context")),
|
||||||
# Make sure we don’t use a malicious ID
|
# Make sure we don’t use a malicious ID
|
||||||
control_plane__in=self.planes,
|
control_plane__in=self.planes,
|
||||||
).first()
|
).first()
|
||||||
|
|
@ -131,37 +131,49 @@ class ServiceOfferingDetailView(OrganizationViewMixin, HtmxViewMixin, DetailView
|
||||||
control_plane=self.selected_plane, service_offering=self.object
|
control_plane=self.selected_plane, service_offering=self.object
|
||||||
).first()
|
).first()
|
||||||
|
|
||||||
def get_instance_form(self):
|
|
||||||
if not self.context_object or not self.context_object.model_form_class:
|
|
||||||
return None
|
|
||||||
|
|
||||||
initial = {
|
def get_instance_form_kwargs(self, ignore_data=False):
|
||||||
|
return {"initial": {
|
||||||
"organization": self.request.organization,
|
"organization": self.request.organization,
|
||||||
"context": self.context_object,
|
"context": self.context_object,
|
||||||
}
|
}, "prefix": "expert", "data": self.request.POST if (self.request.method == "POST" and not ignore_data) else None
|
||||||
|
}
|
||||||
|
|
||||||
# Pre-populate FQDN field if it exists and control plane has wildcard DNS
|
def get_instance_form(self, ignore_data=False):
|
||||||
form_class = self.context_object.model_form_class
|
if not self.context_object or not self.context_object.model_form_class:
|
||||||
if (
|
return
|
||||||
"spec.parameters.service.fqdn" in form_class.base_fields
|
|
||||||
and self.context_object.control_plane.wildcard_dns
|
|
||||||
):
|
|
||||||
# Generate initial FQDN: instancename-namespace.wildcard_dns
|
|
||||||
# We'll set a placeholder that JavaScript will replace dynamically
|
|
||||||
initial["spec.parameters.service.fqdn"] = ""
|
|
||||||
|
|
||||||
return form_class(
|
return self.context_object.model_form_class(**self.get_instance_form_kwargs(ignore_data=ignore_data))
|
||||||
data=self.request.POST if self.request.method == "POST" else None,
|
|
||||||
initial=initial,
|
def get_custom_instance_form(self, ignore_data=False):
|
||||||
)
|
if not self.context_object or not self.context_object.custom_model_form_class:
|
||||||
|
return
|
||||||
|
kwargs = self.get_instance_form_kwargs(ignore_data=ignore_data)
|
||||||
|
kwargs["prefix"] = "custom"
|
||||||
|
return self.context_object.custom_model_form_class(**kwargs)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_custom_form(self):
|
||||||
|
# Note: "custom form" = user-friendly, subset of fields
|
||||||
|
# vs "expert form" = auto-generated (all technical fields)
|
||||||
|
return self.request.POST.get("active_form", "expert") == "custom"
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
context = super().get_context_data(**kwargs)
|
context = super().get_context_data(**kwargs)
|
||||||
context["select_form"] = self.select_form
|
context["select_form"] = self.select_form
|
||||||
context["has_control_planes"] = self.planes.exists()
|
context["has_control_planes"] = self.planes.exists()
|
||||||
context["selected_plane"] = self.selected_plane
|
context["selected_plane"] = self.selected_plane
|
||||||
context["service_form"] = self.get_instance_form()
|
if self.request.method == "POST":
|
||||||
# Pass data for dynamic FQDN generation
|
if self.is_custom_form:
|
||||||
|
context["service_form"] = self.get_instance_form(ignore_data=True)
|
||||||
|
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)
|
||||||
|
else:
|
||||||
|
context["service_form"] = self.get_instance_form()
|
||||||
|
context["custom_service_form"] = self.get_custom_instance_form()
|
||||||
|
|
||||||
if self.selected_plane and self.selected_plane.wildcard_dns:
|
if self.selected_plane and self.selected_plane.wildcard_dns:
|
||||||
context["wildcard_dns"] = self.selected_plane.wildcard_dns
|
context["wildcard_dns"] = self.selected_plane.wildcard_dns
|
||||||
context["organization_namespace"] = self.request.organization.namespace
|
context["organization_namespace"] = self.request.organization.namespace
|
||||||
|
|
@ -175,7 +187,10 @@ class ServiceOfferingDetailView(OrganizationViewMixin, HtmxViewMixin, DetailView
|
||||||
context["form_error"] = True
|
context["form_error"] = True
|
||||||
return self.render_to_response(context)
|
return self.render_to_response(context)
|
||||||
|
|
||||||
form = self.get_instance_form()
|
if self.is_custom_form:
|
||||||
|
form = self.get_custom_instance_form()
|
||||||
|
else:
|
||||||
|
form = self.get_instance_form()
|
||||||
if not form: # Should not happen if context_object is valid, but as a safeguard
|
if not form: # Should not happen if context_object is valid, but as a safeguard
|
||||||
messages.error(
|
messages.error(
|
||||||
self.request,
|
self.request,
|
||||||
|
|
@ -203,8 +218,6 @@ class ServiceOfferingDetailView(OrganizationViewMixin, HtmxViewMixin, DetailView
|
||||||
)
|
)
|
||||||
form.add_error(None, error_message)
|
form.add_error(None, error_message)
|
||||||
|
|
||||||
# If the form is not valid or if the service creation failed, we render it again
|
|
||||||
context["service_form"] = form
|
|
||||||
return self.render_to_response(context)
|
return self.render_to_response(context)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue