diff --git a/pyproject.toml b/pyproject.toml index b89d892..d881deb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -9,7 +9,6 @@ dependencies = [ "cryptography>=45.0.7", "django==5.2.6", "django-allauth>=65.10.0", - "django-auditlog>=3.2.1", "django-fernet-encrypted-fields>=0.3.0", "django-jsonform>=2.23.2", "django-scopes>=2.0.0", diff --git a/src/servala/core/admin.py b/src/servala/core/admin.py index 149635c..ed07574 100644 --- a/src/servala/core/admin.py +++ b/src/servala/core/admin.py @@ -272,8 +272,8 @@ class ControlPlaneCRDAdmin(admin.ModelAdmin): @admin.register(ServiceInstance) class ServiceInstanceAdmin(admin.ModelAdmin): - list_display = ("name", "organization", "context", "created_by") - list_filter = ("organization", "context") + list_display = ("name", "organization", "context", "created_by", "is_deleted") + list_filter = ("organization", "context", "is_deleted") search_fields = ( "name", "organization__name", @@ -296,6 +296,9 @@ class ServiceInstanceAdmin(admin.ModelAdmin): "organization", "context", "created_by", + "is_deleted", + "deleted_at", + "deleted_by", ) }, ), diff --git a/src/servala/core/migrations/0006_remove_service_instance_soft_delete.py b/src/servala/core/migrations/0006_remove_service_instance_soft_delete.py deleted file mode 100644 index 0fcc02a..0000000 --- a/src/servala/core/migrations/0006_remove_service_instance_soft_delete.py +++ /dev/null @@ -1,25 +0,0 @@ -# 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", - ), - ] diff --git a/src/servala/core/models/service.py b/src/servala/core/models/service.py index 6944750..a06db4e 100644 --- a/src/servala/core/models/service.py +++ b/src/servala/core/models/service.py @@ -6,11 +6,11 @@ import re import kubernetes import rules import urlman -from auditlog.registry import auditlog 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,6 +571,16 @@ 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") @@ -784,10 +794,14 @@ class ServiceInstance(ServalaModelMixin, models.Model): raise ValidationError(self.organization.add_support_message(message)) @transaction.atomic - def delete(self, using=None, keep_parents=False, user=None): + def delete_instance(self, user): """ - Deletes the Django instance and the corresponding Kubernetes custom resource. + Soft deletes the instance in Django and initiates deletion of the + corresponding Kubernetes custom resource. """ + if self.is_deleted: + return + if ( self.spec.get("parameters", {}) .get("security", {}) @@ -811,13 +825,19 @@ 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() @@ -922,6 +942,3 @@ class ServiceInstance(ServalaModelMixin, models.Model): return {"error": str(e)} except Exception as e: return {"error": str(e)} - - -auditlog.register(ServiceInstance, exclude_fields=["updated_at"], serialize_data=True) diff --git a/src/servala/frontend/forms/service.py b/src/servala/frontend/forms/service.py index 5dd78a7..ca84ab3 100644 --- a/src/servala/frontend/forms/service.py +++ b/src/servala/frontend/forms/service.py @@ -64,6 +64,14 @@ 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(): @@ -80,6 +88,11 @@ 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 diff --git a/src/servala/frontend/templates/frontend/organizations/dashboard.html b/src/servala/frontend/templates/frontend/organizations/dashboard.html index 01faa69..b0dd273 100644 --- a/src/servala/frontend/templates/frontend/organizations/dashboard.html +++ b/src/servala/frontend/templates/frontend/organizations/dashboard.html @@ -112,6 +112,13 @@ {{ instance.context.service_offering.service.name }} +