Remove ServiceInstance soft-delete #174

Merged
tobru merged 3 commits from 167-drop-soft-delete into main 2025-09-05 13:37:25 +00:00
10 changed files with 42 additions and 105 deletions
Showing only changes of commit d8c2aae223 - Show all commits

View file

@ -272,8 +272,8 @@ class ControlPlaneCRDAdmin(admin.ModelAdmin):
@admin.register(ServiceInstance)
class ServiceInstanceAdmin(admin.ModelAdmin):
list_display = ("name", "organization", "context", "created_by", "is_deleted")
list_filter = ("organization", "context", "is_deleted")
list_display = ("name", "organization", "context", "created_by")
list_filter = ("organization", "context")
search_fields = (
"name",
"organization__name",
@ -296,9 +296,6 @@ class ServiceInstanceAdmin(admin.ModelAdmin):
"organization",
"context",
"created_by",
"is_deleted",
"deleted_at",
"deleted_by",
)
},
),

View file

@ -0,0 +1,25 @@
# Generated by Django 5.2.4 on 2025-09-03 23:08
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
("core", "0005_organization_sale_order_fields"),
]
operations = [
migrations.RemoveField(
model_name="serviceinstance",
name="deleted_at",
),
migrations.RemoveField(
model_name="serviceinstance",
name="deleted_by",
),
migrations.RemoveField(
model_name="serviceinstance",
name="is_deleted",
),
]

View file

@ -10,7 +10,6 @@ from django.conf import settings
from django.core.cache import cache
from django.core.exceptions import ValidationError
from django.db import IntegrityError, models, transaction
from django.utils import timezone
from django.utils.functional import cached_property
from django.utils.safestring import mark_safe
from django.utils.translation import gettext_lazy as _
@ -571,16 +570,6 @@ class ServiceInstance(ServalaModelMixin, models.Model):
on_delete=models.PROTECT,
)
is_deleted = models.BooleanField(default=False)
deleted_at = models.DateTimeField(null=True, blank=True)
deleted_by = models.ForeignKey(
to="core.User",
on_delete=models.SET_NULL,
null=True,
blank=True,
related_name="+",
)
class Meta:
verbose_name = _("Service instance")
verbose_name_plural = _("Service instances")
@ -794,14 +783,10 @@ class ServiceInstance(ServalaModelMixin, models.Model):
raise ValidationError(self.organization.add_support_message(message))
@transaction.atomic
def delete_instance(self, user):
def delete(self, using=None, keep_parents=False, user=None):
"""
Soft deletes the instance in Django and initiates deletion of the
corresponding Kubernetes custom resource.
Deletes the Django instance and the corresponding Kubernetes custom resource.
"""
if self.is_deleted:
return
if (
self.spec.get("parameters", {})
.get("security", {})
@ -825,19 +810,13 @@ class ServiceInstance(ServalaModelMixin, models.Model):
if e.status != 404:
# 404 is fine, the object was deleted already.
raise
self.is_deleted = True
self.deleted_at = timezone.now()
self.deleted_by = user
self.save(
update_fields=["is_deleted", "deleted_at", "deleted_by", "updated_at"]
)
self._clear_kubernetes_caches()
return super().delete(using=using, keep_parents=keep_parents)
@cached_property
def kubernetes_object(self):
"""Fetch the Kubernetes custom resource object"""
if self.is_deleted:
return
try:
api_instance = client.CustomObjectsApi(
self.context.control_plane.get_kubernetes_client()

View file

@ -64,14 +64,6 @@ class ServiceInstanceFilterForm(forms.Form):
required=False,
label=_("Service Provider Zone"),
)
status = forms.ChoiceField(
choices=(
("active", _("Active")),
("deleted", _("Deleted")),
),
required=False,
label=_("Status"),
)
def filter_queryset(self, queryset):
if self.is_valid():
@ -88,11 +80,6 @@ class ServiceInstanceFilterForm(forms.Form):
)
if data.get("control_plane"):
queryset = queryset.filter(context__control_plane=data["control_plane"])
status = data.get("status")
if status == "active":
queryset = queryset.filter(is_deleted=False)
elif status == "deleted":
queryset = queryset.filter(is_deleted=True)
return queryset

View file

@ -112,13 +112,6 @@
<span class="small text-muted">{{ instance.context.service_offering.service.name }}</span>
</div>
</td>
<td>
{% if instance.is_deleted %}
<span class="badge bg-danger">{% translate "Deleted" %}</span>
{% else %}
<span class="badge bg-success">{% translate "Active" %}</span>
{% endif %}
</td>
<td>
<span class="small text-muted">{{ instance.created_at|date:"M d, Y" }}</span>
</td>

View file

@ -7,10 +7,10 @@
{% endblock html_title %}
{% block page_title_extra %}
<div>
{% if has_change_permission and not instance.is_deleted %}
{% if has_change_permission %}
<a href="{{ instance.urls.update }}" class="btn btn-primary me-1 mb-1">{% translate "Edit" %}</a>
{% endif %}
{% if has_delete_permission and not instance.is_deleted %}
{% if has_delete_permission %}
<button type="button"
class="btn btn-danger me-1 mb-1"
hx-get="{{ instance.urls.delete }}"
@ -54,26 +54,11 @@
<dd class="col-sm-8">
{{ instance.updated_at|date:"SHORT_DATETIME_FORMAT" }}
</dd>
<dt class="col-sm-4">{% translate "Status" %}</dt>
<dd class="col-sm-8">
{% if instance.is_deleted %}
<span class="badge text-bg-danger">{% translate "Deleted" %}</span>
{% if instance.deleted_at %}
<small class="text-muted d-block mt-1">
{% blocktranslate with date=instance.deleted_at|date:"SHORT_DATETIME_FORMAT" user=instance.deleted_by|default:_("system") %}
On {{ date }} by {{ user }}
{% endblocktranslate %}
</small>
{% endif %}
{% else %}
<span class="badge text-bg-success">{% translate "Active" %}</span>
{% endif %}
</dd>
</dl>
</div>
</div>
</div>
{% if not instance.is_deleted and instance.status_conditions %}
{% if instance.status_conditions %}
<div class="col-12 col-md-7">
<div class="card">
<div class="card-header">
@ -118,7 +103,7 @@
</div>
</div>
{% endif %}
{% if not instance.is_deleted and instance.spec and spec_fieldsets %}
{% if instance.spec and spec_fieldsets %}
<div class="col-12">
<div class="card">
<div class="card-header">

View file

@ -27,7 +27,6 @@
<th>{% translate "Service Provider" %}</th>
<th>{% translate "Service Provider Zone" %}</th>
<th>{% translate "Created At" %}</th>
<th>{% translate "Status" %}</th>
</tr>
</thead>
<tbody>
@ -40,17 +39,10 @@
<td>{{ instance.context.service_offering.provider.name }}</td>
<td>{{ instance.context.control_plane.name }}</td>
<td>{{ instance.created_at|date:"SHORT_DATETIME_FORMAT" }}</td>
<td>
{% if instance.is_deleted %}
<span class="badge text-bg-secondary">{% translate "Deleted" %}</span>
{% else %}
<span class="badge text-bg-success">{% translate "Active" %}</span>
{% endif %}
</td>
</tr>
{% empty %}
<tr>
<td colspan="6">{% translate "No service instances found." %}</td>
<td colspan="5">{% translate "No service instances found." %}</td>
</tr>
{% endfor %}
</tbody>

View file

@ -1,6 +1,6 @@
from django.conf import settings
from django.contrib.auth.mixins import LoginRequiredMixin
from django.db.models import Count, Q
from django.db.models import Count
from django.shortcuts import redirect, render
from django.urls import reverse_lazy
from django.utils.functional import cached_property
@ -32,9 +32,7 @@ class OrganizationSelectionView(LoginRequiredMixin, TemplateView):
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["user_organizations"] = self.user_organizations.annotate(
instance_count=Count(
"service_instances", filter=Q(service_instances__is_deleted=False)
)
instance_count=Count("service_instances")
)
return context

View file

@ -70,9 +70,7 @@ class OrganizationDashboardView(
context = super().get_context_data(**kwargs)
organization = self.get_object()
service_instances = ServiceInstance.objects.filter(
organization=organization, is_deleted=False
)
service_instances = ServiceInstance.objects.filter(organization=organization)
recent_instances = service_instances.order_by("-created_at")[:5]
for instance in recent_instances:

View file

@ -198,11 +198,7 @@ class ServiceInstanceMixin:
def get_object(self, **kwargs):
instance = super().get_object(**kwargs)
if (
not instance.is_deleted
and not instance.kubernetes_object
and not self._has_warned
):
if not instance.kubernetes_object and not self._has_warned:
messages.warning(
self.request,
_(
@ -221,11 +217,7 @@ class ServiceInstanceDetailView(
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
if (
not self.object.is_deleted
and self.object.kubernetes_object
and self.object.spec
):
if self.object.kubernetes_object and self.object.spec:
context["spec_fieldsets"] = self.get_nested_spec()
context["has_change_permission"] = self.request.user.has_perm(
ServiceInstance.get_perm("change"), self.object
@ -406,15 +398,6 @@ class ServiceInstanceListView(OrganizationViewMixin, ListView):
)
if self.filter_form.is_valid():
queryset = self.filter_form.filter_queryset(queryset)
status_filter = (
self.filter_form.cleaned_data.get("status")
if self.filter_form.is_valid()
else "active"
)
if status_filter == "active":
queryset = queryset.filter(is_deleted=False)
elif status_filter == "deleted":
queryset = queryset.filter(is_deleted=True)
return queryset.order_by("-created_at")
def get_context_data(self, **kwargs):
@ -433,7 +416,7 @@ class ServiceInstanceDeleteView(
def form_valid(self, form):
try:
self.object.delete_instance(user=self.request.user)
self.object.delete(user=self.request.user)
messages.success(
self.request,
_("Service instance '{name}' has been scheduled for deletion.").format(