193 lines
7.5 KiB
Python
193 lines
7.5 KiB
Python
# Copyright (c) 2023-present Plane Software, Inc. and contributors
|
|
# SPDX-License-Identifier: AGPL-3.0-only
|
|
# See the LICENSE file for details.
|
|
|
|
# Django imports
|
|
from django.utils import timezone
|
|
from django.apps import apps
|
|
from django.conf import settings
|
|
from django.db import models
|
|
from django.db.models.fields.related import OneToOneRel
|
|
|
|
|
|
# Third party imports
|
|
from celery import shared_task
|
|
|
|
|
|
@shared_task
|
|
def soft_delete_related_objects(app_label, model_name, instance_pk, using=None):
|
|
"""
|
|
Soft delete related objects for a given model instance
|
|
"""
|
|
# Get the model class using app registry
|
|
model_class = apps.get_model(app_label, model_name)
|
|
|
|
# Get the instance using all_objects to ensure we can get even if it's already soft deleted
|
|
try:
|
|
instance = model_class.all_objects.get(pk=instance_pk)
|
|
except model_class.DoesNotExist:
|
|
return
|
|
|
|
# Get all related fields that are reverse relationships
|
|
all_related = [
|
|
f for f in instance._meta.get_fields() if (f.one_to_many or f.one_to_one) and f.auto_created and not f.concrete
|
|
]
|
|
|
|
# Handle each related field
|
|
for relation in all_related:
|
|
related_name = relation.get_accessor_name()
|
|
|
|
# Skip if the relation doesn't exist
|
|
if not hasattr(instance, related_name):
|
|
continue
|
|
|
|
# Get the on_delete behavior name
|
|
on_delete_name = relation.on_delete.__name__ if hasattr(relation.on_delete, "__name__") else ""
|
|
|
|
if on_delete_name == "DO_NOTHING":
|
|
continue
|
|
|
|
elif on_delete_name == "SET_NULL":
|
|
# Handle SET_NULL relationships
|
|
if isinstance(relation, OneToOneRel):
|
|
# For OneToOne relationships
|
|
related_obj = getattr(instance, related_name, None)
|
|
if related_obj and isinstance(related_obj, models.Model):
|
|
setattr(related_obj, relation.remote_field.name, None)
|
|
related_obj.save(update_fields=[relation.remote_field.name])
|
|
else:
|
|
# For other relationships
|
|
related_queryset = getattr(instance, related_name).all()
|
|
related_queryset.update(**{relation.remote_field.name: None})
|
|
|
|
else:
|
|
# Handle CASCADE and other delete behaviors
|
|
try:
|
|
if relation.one_to_one:
|
|
# Handle OneToOne relationships
|
|
related_obj = getattr(instance, related_name, None)
|
|
if related_obj:
|
|
if hasattr(related_obj, "deleted_at"):
|
|
if not related_obj.deleted_at:
|
|
related_obj.deleted_at = timezone.now()
|
|
related_obj.save()
|
|
# Recursively handle related objects
|
|
soft_delete_related_objects(
|
|
related_obj._meta.app_label,
|
|
related_obj._meta.model_name,
|
|
related_obj.pk,
|
|
using,
|
|
)
|
|
else:
|
|
# Handle other relationships
|
|
related_queryset = getattr(instance, related_name)(manager="objects").all()
|
|
|
|
for related_obj in related_queryset:
|
|
if hasattr(related_obj, "deleted_at"):
|
|
if not related_obj.deleted_at:
|
|
related_obj.deleted_at = timezone.now()
|
|
related_obj.save()
|
|
# Recursively handle related objects
|
|
soft_delete_related_objects(
|
|
related_obj._meta.app_label,
|
|
related_obj._meta.model_name,
|
|
related_obj.pk,
|
|
using,
|
|
)
|
|
except Exception as e:
|
|
# Log the error or handle as needed
|
|
print(f"Error handling relation {related_name}: {str(e)}")
|
|
continue
|
|
|
|
# Finally, soft delete the instance itself if it hasn't been deleted yet
|
|
if hasattr(instance, "deleted_at") and not instance.deleted_at:
|
|
instance.deleted_at = timezone.now()
|
|
instance.save()
|
|
|
|
|
|
# @shared_task
|
|
def restore_related_objects(app_label, model_name, instance_pk, using=None):
|
|
pass
|
|
|
|
|
|
@shared_task
|
|
def hard_delete():
|
|
from plane.db.models import (
|
|
Workspace,
|
|
Project,
|
|
Cycle,
|
|
Module,
|
|
Issue,
|
|
Page,
|
|
IssueView,
|
|
Label,
|
|
State,
|
|
IssueActivity,
|
|
IssueComment,
|
|
IssueLink,
|
|
IssueReaction,
|
|
UserFavorite,
|
|
ModuleIssue,
|
|
CycleIssue,
|
|
Estimate,
|
|
EstimatePoint,
|
|
)
|
|
|
|
days = settings.HARD_DELETE_AFTER_DAYS
|
|
# check delete workspace
|
|
_ = Workspace.all_objects.filter(deleted_at__lt=timezone.now() - timezone.timedelta(days=days)).delete()
|
|
|
|
# check delete project
|
|
_ = Project.all_objects.filter(deleted_at__lt=timezone.now() - timezone.timedelta(days=days)).delete()
|
|
|
|
# check delete cycle
|
|
_ = Cycle.all_objects.filter(deleted_at__lt=timezone.now() - timezone.timedelta(days=days)).delete()
|
|
|
|
# check delete module
|
|
_ = Module.all_objects.filter(deleted_at__lt=timezone.now() - timezone.timedelta(days=days)).delete()
|
|
|
|
# check delete issue
|
|
_ = Issue.all_objects.filter(deleted_at__lt=timezone.now() - timezone.timedelta(days=days)).delete()
|
|
|
|
# check delete page
|
|
_ = Page.all_objects.filter(deleted_at__lt=timezone.now() - timezone.timedelta(days=days)).delete()
|
|
|
|
# check delete view
|
|
_ = IssueView.all_objects.filter(deleted_at__lt=timezone.now() - timezone.timedelta(days=days)).delete()
|
|
|
|
# check delete label
|
|
_ = Label.all_objects.filter(deleted_at__lt=timezone.now() - timezone.timedelta(days=days)).delete()
|
|
|
|
# check delete state
|
|
_ = State.all_objects.filter(deleted_at__lt=timezone.now() - timezone.timedelta(days=days)).delete()
|
|
|
|
_ = IssueActivity.all_objects.filter(deleted_at__lt=timezone.now() - timezone.timedelta(days=days)).delete()
|
|
|
|
_ = IssueComment.all_objects.filter(deleted_at__lt=timezone.now() - timezone.timedelta(days=days)).delete()
|
|
|
|
_ = IssueLink.all_objects.filter(deleted_at__lt=timezone.now() - timezone.timedelta(days=days)).delete()
|
|
|
|
_ = IssueReaction.all_objects.filter(deleted_at__lt=timezone.now() - timezone.timedelta(days=days)).delete()
|
|
|
|
_ = UserFavorite.all_objects.filter(deleted_at__lt=timezone.now() - timezone.timedelta(days=days)).delete()
|
|
|
|
_ = ModuleIssue.all_objects.filter(deleted_at__lt=timezone.now() - timezone.timedelta(days=days)).delete()
|
|
|
|
_ = CycleIssue.all_objects.filter(deleted_at__lt=timezone.now() - timezone.timedelta(days=days)).delete()
|
|
|
|
_ = Estimate.all_objects.filter(deleted_at__lt=timezone.now() - timezone.timedelta(days=days)).delete()
|
|
|
|
_ = EstimatePoint.all_objects.filter(deleted_at__lt=timezone.now() - timezone.timedelta(days=days)).delete()
|
|
|
|
# at last, check for every thing which ever is left and delete it
|
|
# Get all Django models
|
|
all_models = apps.get_models()
|
|
|
|
# Iterate through all models
|
|
for model in all_models:
|
|
# Check if the model has a 'deleted_at' field
|
|
if hasattr(model, "deleted_at"):
|
|
# Get all instances where 'deleted_at' is greater than 30 days ago
|
|
_ = model.all_objects.filter(deleted_at__lt=timezone.now() - timezone.timedelta(days=days)).delete()
|
|
|
|
return
|