Instance form and template improvements
This commit is contained in:
parent
52aa6acfb6
commit
3e466fb011
5 changed files with 82 additions and 44 deletions
|
@ -374,9 +374,11 @@ class ControlPlaneCRD(ServalaModelMixin, models.Model):
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def kind_plural(self):
|
def kind_plural(self):
|
||||||
if hasattr(self.resource_definition, 'status') and \
|
if (
|
||||||
hasattr(self.resource_definition.status, 'accepted_names') and \
|
hasattr(self.resource_definition, "status")
|
||||||
self.resource_definition.status.accepted_names:
|
and hasattr(self.resource_definition.status, "accepted_names")
|
||||||
|
and self.resource_definition.status.accepted_names
|
||||||
|
):
|
||||||
return self.resource_definition.status.accepted_names.plural
|
return self.resource_definition.status.accepted_names.plural
|
||||||
if self.kind.endswith("s"):
|
if self.kind.endswith("s"):
|
||||||
return self.kind.lower()
|
return self.kind.lower()
|
||||||
|
@ -648,7 +650,9 @@ class ServiceInstance(ServalaModelMixin, models.Model):
|
||||||
self.is_deleted = True
|
self.is_deleted = True
|
||||||
self.deleted_at = timezone.now()
|
self.deleted_at = timezone.now()
|
||||||
self.deleted_by = user
|
self.deleted_by = user
|
||||||
self.save(update_fields=["is_deleted", "deleted_at", "deleted_by", "updated_at"])
|
self.save(
|
||||||
|
update_fields=["is_deleted", "deleted_at", "deleted_by", "updated_at"]
|
||||||
|
)
|
||||||
|
|
||||||
self._clear_kubernetes_caches()
|
self._clear_kubernetes_caches()
|
||||||
|
|
||||||
|
|
|
@ -6,6 +6,7 @@ from servala.core.models import (
|
||||||
ControlPlane,
|
ControlPlane,
|
||||||
Service,
|
Service,
|
||||||
ServiceCategory,
|
ServiceCategory,
|
||||||
|
ServiceInstance,
|
||||||
ServiceOffering,
|
ServiceOffering,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -37,6 +38,8 @@ class ControlPlaneSelectForm(forms.Form):
|
||||||
def __init__(self, *args, planes=None, **kwargs):
|
def __init__(self, *args, planes=None, **kwargs):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
self.fields["control_plane"].queryset = planes
|
self.fields["control_plane"].queryset = planes
|
||||||
|
if planes and planes.count() == 1:
|
||||||
|
self.fields["control_plane"].initial = planes.first()
|
||||||
|
|
||||||
|
|
||||||
class ServiceInstanceFilterForm(forms.Form):
|
class ServiceInstanceFilterForm(forms.Form):
|
||||||
|
@ -68,23 +71,23 @@ class ServiceInstanceFilterForm(forms.Form):
|
||||||
def filter_queryset(self, queryset):
|
def filter_queryset(self, queryset):
|
||||||
if self.is_valid():
|
if self.is_valid():
|
||||||
data = self.cleaned_data
|
data = self.cleaned_data
|
||||||
if data["name"]:
|
if data.get("name"):
|
||||||
queryset = queryset.filter(name__icontains=data["name"])
|
queryset = queryset.filter(name__icontains=data["name"])
|
||||||
if data["service"]:
|
if data.get("service"):
|
||||||
queryset = queryset.filter(
|
queryset = queryset.filter(
|
||||||
context__service_definition__service=data["service"]
|
context__service_definition__service=data["service"]
|
||||||
)
|
)
|
||||||
if data["provider"]:
|
if data.get("provider"):
|
||||||
queryset = queryset.filter(
|
queryset = queryset.filter(
|
||||||
context__service_offering__provider=data["provider"]
|
context__service_offering__provider=data["provider"]
|
||||||
)
|
)
|
||||||
if data["control_plane"]:
|
if data.get("control_plane"):
|
||||||
queryset = queryset.filter(context__control_plane=data["control_plane"])
|
queryset = queryset.filter(context__control_plane=data["control_plane"])
|
||||||
if data["status"]:
|
status = data.get("status")
|
||||||
if data["status"] == "active":
|
if status == "active":
|
||||||
queryset = queryset.filter(is_deleted=False)
|
queryset = queryset.filter(is_deleted=False)
|
||||||
else:
|
elif status == "deleted":
|
||||||
queryset = queryset.filter(is_deleted=True)
|
queryset = queryset.filter(is_deleted=True)
|
||||||
return queryset
|
return queryset
|
||||||
|
|
||||||
|
|
||||||
|
@ -105,11 +108,13 @@ class ServiceInstanceDeleteForm(forms.ModelForm):
|
||||||
def clean_name(self):
|
def clean_name(self):
|
||||||
entered_name = self.cleaned_data.get("name")
|
entered_name = self.cleaned_data.get("name")
|
||||||
if entered_name != self.instance.name:
|
if entered_name != self.instance.name:
|
||||||
raise ValidationError(
|
raise forms.ValidationError(
|
||||||
_("The entered name does not match the instance name. Deletion not confirmed.")
|
_(
|
||||||
|
"The entered name does not match the instance name. Deletion not confirmed."
|
||||||
|
)
|
||||||
)
|
)
|
||||||
return entered_name
|
return entered_name
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = ServiceInstance
|
model = ServiceInstance
|
||||||
fields = ("name", )
|
fields = ("name",)
|
||||||
|
|
|
@ -1,11 +1,18 @@
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
<form method="post" action="{{ view.request.path }}" hx-post="{{ view.request.path }}" hx-target="this" hx-swap="outerHTML">
|
<form method="post"
|
||||||
|
action="{{ view.request.path }}"
|
||||||
|
hx-post="{{ view.request.path }}"
|
||||||
|
hx-target="this"
|
||||||
|
hx-swap="outerHTML">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<div class="modal-header">
|
<div class="modal-header">
|
||||||
<h5 class="modal-title" id="deleteInstanceModalLabel">
|
<h5 class="modal-title" id="deleteInstanceModalLabel">
|
||||||
{% blocktranslate with instance_name=instance.name %}Confirm Deletion of {{ instance_name }}{% endblocktranslate %}
|
{% blocktranslate with instance_name=instance.name %}Confirm Deletion of {{ instance_name }}{% endblocktranslate %}
|
||||||
</h5>
|
</h5>
|
||||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
<button type="button"
|
||||||
|
class="btn-close"
|
||||||
|
data-bs-dismiss="modal"
|
||||||
|
aria-label="Close"></button>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-body hide-form-errors">
|
<div class="modal-body hide-form-errors">
|
||||||
<p>{% translate "Do you really want to delete this service instance? This action cannot be undone." %}</p>
|
<p>{% translate "Do you really want to delete this service instance? This action cannot be undone." %}</p>
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
{% extends "frontend/base.html" %}
|
{% extends "frontend/base.html" %}
|
||||||
{% load i18n static %}
|
{% load i18n static pprint_filters %}
|
||||||
{% block html_title %}
|
{% block html_title %}
|
||||||
{% block page_title %}
|
{% block page_title %}
|
||||||
{{ instance.name }}
|
{{ instance.name }}
|
||||||
|
@ -11,13 +11,12 @@
|
||||||
<a href="{{ instance.urls.update }}" class="btn btn-primary me-1 mb-1">{% translate "Edit" %}</a>
|
<a href="{{ instance.urls.update }}" class="btn btn-primary me-1 mb-1">{% translate "Edit" %}</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if has_delete_permission and not instance.is_deleted %}
|
{% if has_delete_permission and not instance.is_deleted %}
|
||||||
<button type="button" class="btn btn-danger me-1 mb-1"
|
<button type="button"
|
||||||
|
class="btn btn-danger me-1 mb-1"
|
||||||
hx-get="{{ instance.urls.delete }}"
|
hx-get="{{ instance.urls.delete }}"
|
||||||
hx-target="#deleteInstanceModalContent"
|
hx-target="#deleteInstanceModalContent"
|
||||||
data-bs-toggle="modal"
|
data-bs-toggle="modal"
|
||||||
data-bs-target="#deleteInstanceModal">
|
data-bs-target="#deleteInstanceModal">{% translate "Delete" %}</button>
|
||||||
{% translate "Delete" %}
|
|
||||||
</button>
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
{% endblock page_title_extra %}
|
{% endblock page_title_extra %}
|
||||||
|
@ -45,7 +44,7 @@
|
||||||
</dd>
|
</dd>
|
||||||
<dt class="col-sm-4">{% translate "Created By" %}</dt>
|
<dt class="col-sm-4">{% translate "Created By" %}</dt>
|
||||||
<dd class="col-sm-8">
|
<dd class="col-sm-8">
|
||||||
{{ instance.created_by }}
|
{{ instance.created_by|default:"-" }}
|
||||||
</dd>
|
</dd>
|
||||||
<dt class="col-sm-4">{% translate "Created At" %}</dt>
|
<dt class="col-sm-4">{% translate "Created At" %}</dt>
|
||||||
<dd class="col-sm-8">
|
<dd class="col-sm-8">
|
||||||
|
@ -106,9 +105,9 @@
|
||||||
<span class="badge text-bg-secondary">{{ condition.status }}</span>
|
<span class="badge text-bg-secondary">{{ condition.status }}</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
<td>{{ condition.lastTransitionTime }}</td>
|
<td>{{ condition.lastTransitionTime|date:"SHORT_DATETIME_FORMAT" }}</td>
|
||||||
<td>{{ condition.reason }}</td>
|
<td>{{ condition.reason|default:"-" }}</td>
|
||||||
<td>{{ condition.message }}</td>
|
<td>{{ condition.message|truncatewords:20|default:"-" }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
|
@ -137,6 +136,8 @@
|
||||||
data-bs-toggle="tab"
|
data-bs-toggle="tab"
|
||||||
role="tab">{{ fieldset.title }}</a>
|
role="tab">{{ fieldset.title }}</a>
|
||||||
</li>
|
</li>
|
||||||
|
{% empty %}
|
||||||
|
<li class="nav-item ms-2">{% translate "No specification details available." %}</li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ul>
|
</ul>
|
||||||
<!-- Tab Content -->
|
<!-- Tab Content -->
|
||||||
|
@ -153,7 +154,7 @@
|
||||||
{% if field.value|default:""|stringformat:"s"|slice:":1" == "{" or field.value|default:""|stringformat:"s"|slice:":1" == "[" %}
|
{% if field.value|default:""|stringformat:"s"|slice:":1" == "{" or field.value|default:""|stringformat:"s"|slice:":1" == "[" %}
|
||||||
<pre>{{ field.value|pprint }}</pre>
|
<pre>{{ field.value|pprint }}</pre>
|
||||||
{% else %}
|
{% else %}
|
||||||
{{ field.value }}
|
{{ field.value|default:"-" }}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</dd>
|
</dd>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
@ -168,13 +169,15 @@
|
||||||
{% if field.value|default:""|stringformat:"s"|slice:":1" == "{" or field.value|default:""|stringformat:"s"|slice:":1" == "[" %}
|
{% if field.value|default:""|stringformat:"s"|slice:":1" == "{" or field.value|default:""|stringformat:"s"|slice:":1" == "[" %}
|
||||||
<pre>{{ field.value|pprint }}</pre>
|
<pre>{{ field.value|pprint }}</pre>
|
||||||
{% else %}
|
{% else %}
|
||||||
{{ field.value }}
|
{{ field.value|default:"-" }}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</dd>
|
</dd>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</dl>
|
</dl>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
|
{% empty %}
|
||||||
|
<p>{% translate "No specification details to display." %}</p>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -218,15 +221,21 @@
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<!-- Delete Confirmation Modal -->
|
<!-- Delete Confirmation Modal -->
|
||||||
<div class="modal fade" id="deleteInstanceModal" tabindex="-1" aria-labelledby="deleteInstanceModalLabel" aria-hidden="true">
|
<div class="modal fade"
|
||||||
|
id="deleteInstanceModal"
|
||||||
|
tabindex="-1"
|
||||||
|
aria-labelledby="deleteInstanceModalLabel"
|
||||||
|
aria-hidden="true">
|
||||||
<div class="modal-dialog modal-dialog-centered">
|
<div class="modal-dialog modal-dialog-centered">
|
||||||
<div class="modal-content" id="deleteInstanceModalContent">
|
<div class="modal-content" id="deleteInstanceModalContent">
|
||||||
{# Content will be loaded here by HTMX #}
|
{# Content will be loaded here by HTMX #}
|
||||||
<div class="modal-header">
|
<div class="modal-header">
|
||||||
<h5 class="modal-title" id="deleteInstanceModalLabel">{% translate "Confirm Deletion" %}</h5>
|
<h5 class="modal-title" id="deleteInstanceModalLabel">{% translate "Confirm Deletion" %}</h5>
|
||||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
<button type="button"
|
||||||
|
class="btn-close"
|
||||||
|
data-bs-dismiss="modal"
|
||||||
|
aria-label="Close"></button>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<div class="d-flex justify-content-center">
|
<div class="d-flex justify-content-center">
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
from django.contrib import messages
|
from django.contrib import messages
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
|
from django.http import HttpResponse
|
||||||
from django.shortcuts import redirect
|
from django.shortcuts import redirect
|
||||||
from django.utils.functional import cached_property
|
from django.utils.functional import cached_property
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from django.views.generic import DetailView, UpdateView, ListView
|
from django.views.generic import DetailView, ListView, UpdateView
|
||||||
|
|
||||||
from servala.core.crd import deslugify
|
from servala.core.crd import deslugify
|
||||||
from servala.core.models import (
|
from servala.core.models import (
|
||||||
|
@ -35,10 +36,14 @@ class ServiceListView(OrganizationViewMixin, ListView):
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
"""Return all services."""
|
"""Return all services."""
|
||||||
services = Service.objects.all().select_related("category")
|
services = (
|
||||||
|
Service.objects.all()
|
||||||
|
.select_related("category")
|
||||||
|
.prefetch_related("offerings__provider")
|
||||||
|
)
|
||||||
if self.filter_form.is_valid():
|
if self.filter_form.is_valid():
|
||||||
services = self.filter_form.filter_queryset(services)
|
services = self.filter_form.filter_queryset(services)
|
||||||
return services
|
return services.distinct()
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def filter_form(self):
|
def filter_form(self):
|
||||||
|
@ -93,7 +98,7 @@ class ServiceOfferingDetailView(OrganizationViewMixin, HtmxViewMixin, DetailView
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def selected_plane(self):
|
def selected_plane(self):
|
||||||
if self.select_form.data and self.select_form.is_valid():
|
if self.select_form.is_valid() and self.select_form.cleaned_data:
|
||||||
return self.select_form.cleaned_data["control_plane"]
|
return self.select_form.cleaned_data["control_plane"]
|
||||||
field = self.select_form.fields["control_plane"]
|
field = self.select_form.fields["control_plane"]
|
||||||
return field.initial or field.queryset.first()
|
return field.initial or field.queryset.first()
|
||||||
|
@ -111,7 +116,7 @@ class ServiceOfferingDetailView(OrganizationViewMixin, HtmxViewMixin, DetailView
|
||||||
).first()
|
).first()
|
||||||
|
|
||||||
def get_instance_form(self):
|
def get_instance_form(self):
|
||||||
if not self.context_object:
|
if not self.context_object or not self.context_object.model_form_class:
|
||||||
return None
|
return None
|
||||||
return self.context_object.model_form_class(
|
return self.context_object.model_form_class(
|
||||||
data=self.request.POST if self.request.method == "POST" else None,
|
data=self.request.POST if self.request.method == "POST" else None,
|
||||||
|
@ -208,7 +213,11 @@ class ServiceInstanceDetailView(
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
context = super().get_context_data(**kwargs)
|
context = super().get_context_data(**kwargs)
|
||||||
if not self.object.is_deleted and self.object.kubernetes_object and self.object.spec:
|
if (
|
||||||
|
not self.object.is_deleted
|
||||||
|
and self.object.kubernetes_object
|
||||||
|
and self.object.spec
|
||||||
|
):
|
||||||
context["spec_fieldsets"] = self.get_nested_spec()
|
context["spec_fieldsets"] = self.get_nested_spec()
|
||||||
context["has_change_permission"] = self.request.user.has_perm(
|
context["has_change_permission"] = self.request.user.has_perm(
|
||||||
ServiceInstance.get_perm("change"), self.object
|
ServiceInstance.get_perm("change"), self.object
|
||||||
|
@ -383,11 +392,15 @@ class ServiceInstanceListView(OrganizationViewMixin, ListView):
|
||||||
)
|
)
|
||||||
if self.filter_form.is_valid():
|
if self.filter_form.is_valid():
|
||||||
queryset = self.filter_form.filter_queryset(queryset)
|
queryset = self.filter_form.filter_queryset(queryset)
|
||||||
status_filter = self.filter_form.cleaned_data.get("status") if self.filter_form.is_valid() else "active"
|
status_filter = (
|
||||||
|
self.filter_form.cleaned_data.get("status")
|
||||||
|
if self.filter_form.is_valid()
|
||||||
|
else "active"
|
||||||
|
)
|
||||||
if status_filter == "active":
|
if status_filter == "active":
|
||||||
queryset = queryset.filter(is_deleted=False)
|
queryset = queryset.filter(is_deleted=False)
|
||||||
elif status_filter == "deleted":
|
elif status_filter == "deleted":
|
||||||
queryset = queryset.filter(is_deleted=True)
|
queryset = queryset.filter(is_deleted=True)
|
||||||
return queryset.order_by("-created_at")
|
return queryset.order_by("-created_at")
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
|
@ -419,9 +432,9 @@ class ServiceInstanceDeleteView(
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
messages.error(
|
messages.error(
|
||||||
self.request,
|
self.request,
|
||||||
_("An error occurred while trying to delete instance '{name}': {error}").format(
|
_(
|
||||||
name=self.object.name, error=str(e)
|
"An error occurred while trying to delete instance '{name}': {error}"
|
||||||
),
|
).format(name=self.object.name, error=str(e)),
|
||||||
)
|
)
|
||||||
response = HttpResponse()
|
response = HttpResponse()
|
||||||
response["HX-Redirect"] = str(self.object.urls.base)
|
response["HX-Redirect"] = str(self.object.urls.base)
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue