From d8958f0e71589ecf7a2ecb4631d7a03679791324 Mon Sep 17 00:00:00 2001 From: NarayanBavisetti Date: Mon, 20 May 2024 17:10:55 +0530 Subject: [PATCH 1/4] chore: soft delete opration --- apiserver/plane/app/views/issue/base.py | 5 +- apiserver/plane/app/views/view/base.py | 1 + apiserver/plane/bgtasks/deletion_task.py | 113 +++++ apiserver/plane/bgtasks/notification_task.py | 1 - apiserver/plane/celery.py | 4 + ...d_at_apiactivitylog_deleted_at_and_more.py | 388 ++++++++++++++++++ apiserver/plane/db/mixins.py | 46 ++- apiserver/plane/db/models/cycle.py | 1 + apiserver/plane/db/models/issue.py | 1 + apiserver/plane/db/models/module.py | 1 + apiserver/plane/db/models/page.py | 2 + apiserver/plane/db/models/project.py | 1 + apiserver/plane/db/models/view.py | 4 +- ...ed_at_instanceadmin_deleted_at_and_more.py | 28 ++ 14 files changed, 589 insertions(+), 7 deletions(-) create mode 100644 apiserver/plane/bgtasks/deletion_task.py create mode 100644 apiserver/plane/db/migrations/0066_analyticview_deleted_at_apiactivitylog_deleted_at_and_more.py create mode 100644 apiserver/plane/license/migrations/0002_instance_deleted_at_instanceadmin_deleted_at_and_more.py diff --git a/apiserver/plane/app/views/issue/base.py b/apiserver/plane/app/views/issue/base.py index fad85b79da1..b703aa27566 100644 --- a/apiserver/plane/app/views/issue/base.py +++ b/apiserver/plane/app/views/issue/base.py @@ -241,6 +241,7 @@ def get(self, request, slug, project_id): "link_count", "is_draft", "archived_at", + "deleted_at", ) datetime_fields = ["created_at", "updated_at"] issues = user_timezone_converter( @@ -329,7 +330,7 @@ def get_queryset(self): filter=~Q(issue_module__module_id__isnull=True), ), Value([], output_field=ArrayField(UUIDField())), - ), + ), ) ).distinct() @@ -444,6 +445,7 @@ def list(self, request, slug, project_id): "link_count", "is_draft", "archived_at", + "deleted_at", ) datetime_fields = ["created_at", "updated_at"] issues = user_timezone_converter( @@ -509,6 +511,7 @@ def create(self, request, slug, project_id): "link_count", "is_draft", "archived_at", + "deleted_at", ) .first() ) diff --git a/apiserver/plane/app/views/view/base.py b/apiserver/plane/app/views/view/base.py index 72c27d20a48..d50c3c29ff0 100644 --- a/apiserver/plane/app/views/view/base.py +++ b/apiserver/plane/app/views/view/base.py @@ -253,6 +253,7 @@ def list(self, request, slug): "link_count", "is_draft", "archived_at", + "deleted_at", ) datetime_fields = ["created_at", "updated_at"] issues = user_timezone_converter( diff --git a/apiserver/plane/bgtasks/deletion_task.py b/apiserver/plane/bgtasks/deletion_task.py new file mode 100644 index 00000000000..1306d02c82c --- /dev/null +++ b/apiserver/plane/bgtasks/deletion_task.py @@ -0,0 +1,113 @@ +# Django imports +from django.utils import timezone +from django.apps import apps +from django.core.exceptions import ObjectDoesNotExist + +# Third party imports +from celery import shared_task + + +@shared_task +def soft_delete_related_objects( + app_label, model_name, instance_pk, using=None +): + model_class = apps.get_model(app_label, model_name) + instance = model_class.all_objects.get(pk=instance_pk) + related_fields = instance._meta.get_fields() + for field in related_fields: + if field.one_to_many or field.one_to_one or field.many_to_many: + try: + if field.one_to_many or field.many_to_many: + related_objects = getattr(instance, field.name).all() + elif field.one_to_one: + related_object = getattr(instance, field.name) + related_objects = ( + [related_object] if related_object is not None else [] + ) + for obj in related_objects: + if obj: + obj.deleted_at = timezone.now() + obj.save(using=using) + except ObjectDoesNotExist: + pass + + +# @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, + ) + + # check delete workspace + _ = Workspace.all_objects.filter( + deleted_at__lt=timezone.now() - timezone.timedelta(days=0) + ).delete() + + # check delete project + _ = Project.all_objects.filter( + deleted_at__lt=timezone.now() - timezone.timedelta(days=0) + ).delete() + + # check delete cycle + _ = Cycle.all_objects.filter( + deleted_at__lt=timezone.now() - timezone.timedelta(days=0) + ).delete() + + # check delete module + _ = Module.all_objects.filter( + deleted_at__lt=timezone.now() - timezone.timedelta(days=0) + ).delete() + + # check delete issue + _ = Issue.all_objects.filter( + deleted_at__lt=timezone.now() - timezone.timedelta(days=0) + ).delete() + + # check delete page + _ = Page.all_objects.filter( + deleted_at__lt=timezone.now() - timezone.timedelta(days=0) + ).delete() + + # check delete view + _ = IssueView.all_objects.filter( + deleted_at__lt=timezone.now() - timezone.timedelta(days=0) + ).delete() + + # check delete label + _ = Label.all_objects.filter( + deleted_at__lt=timezone.now() - timezone.timedelta(days=0) + ).delete() + + # check delete state + _ = State.all_objects.filter( + deleted_at__lt=timezone.now() - timezone.timedelta(days=0) + ).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=0) + ).delete() + + return diff --git a/apiserver/plane/bgtasks/notification_task.py b/apiserver/plane/bgtasks/notification_task.py index 9dfd0c16dc8..0189316cf3c 100644 --- a/apiserver/plane/bgtasks/notification_task.py +++ b/apiserver/plane/bgtasks/notification_task.py @@ -221,7 +221,6 @@ def notifications( else None ) if type not in [ - "issue.activity.deleted", "cycle.activity.created", "cycle.activity.deleted", "module.activity.created", diff --git a/apiserver/plane/celery.py b/apiserver/plane/celery.py index d3e742f14a9..36d3942edc2 100644 --- a/apiserver/plane/celery.py +++ b/apiserver/plane/celery.py @@ -36,6 +36,10 @@ "task": "plane.bgtasks.api_logs_task.delete_api_logs", "schedule": crontab(hour=0, minute=0), }, + "check-every-day-to-delete-hard-delete": { + "task": "plane.bgtasks.deletion_task.hard_delete", + "schedule": crontab(hour=11, minute=9), + }, } # Load task modules from all registered Django app configs. diff --git a/apiserver/plane/db/migrations/0066_analyticview_deleted_at_apiactivitylog_deleted_at_and_more.py b/apiserver/plane/db/migrations/0066_analyticview_deleted_at_apiactivitylog_deleted_at_and_more.py new file mode 100644 index 00000000000..82bfe5045e2 --- /dev/null +++ b/apiserver/plane/db/migrations/0066_analyticview_deleted_at_apiactivitylog_deleted_at_and_more.py @@ -0,0 +1,388 @@ +# Generated by Django 4.2.11 on 2024-05-13 11:01 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('db', '0065_auto_20240415_0937'), + ] + + operations = [ + migrations.AddField( + model_name='analyticview', + name='deleted_at', + field=models.DateTimeField(blank=True, null=True, verbose_name='Deleted At'), + ), + migrations.AddField( + model_name='apiactivitylog', + name='deleted_at', + field=models.DateTimeField(blank=True, null=True, verbose_name='Deleted At'), + ), + migrations.AddField( + model_name='apitoken', + name='deleted_at', + field=models.DateTimeField(blank=True, null=True, verbose_name='Deleted At'), + ), + migrations.AddField( + model_name='commentreaction', + name='deleted_at', + field=models.DateTimeField(blank=True, null=True, verbose_name='Deleted At'), + ), + migrations.AddField( + model_name='cycle', + name='deleted_at', + field=models.DateTimeField(blank=True, null=True, verbose_name='Deleted At'), + ), + migrations.AddField( + model_name='cyclefavorite', + name='deleted_at', + field=models.DateTimeField(blank=True, null=True, verbose_name='Deleted At'), + ), + migrations.AddField( + model_name='cycleissue', + name='deleted_at', + field=models.DateTimeField(blank=True, null=True, verbose_name='Deleted At'), + ), + migrations.AddField( + model_name='cycleuserproperties', + name='deleted_at', + field=models.DateTimeField(blank=True, null=True, verbose_name='Deleted At'), + ), + migrations.AddField( + model_name='dashboard', + name='deleted_at', + field=models.DateTimeField(blank=True, null=True, verbose_name='Deleted At'), + ), + migrations.AddField( + model_name='dashboardwidget', + name='deleted_at', + field=models.DateTimeField(blank=True, null=True, verbose_name='Deleted At'), + ), + migrations.AddField( + model_name='emailnotificationlog', + name='deleted_at', + field=models.DateTimeField(blank=True, null=True, verbose_name='Deleted At'), + ), + migrations.AddField( + model_name='estimate', + name='deleted_at', + field=models.DateTimeField(blank=True, null=True, verbose_name='Deleted At'), + ), + migrations.AddField( + model_name='estimatepoint', + name='deleted_at', + field=models.DateTimeField(blank=True, null=True, verbose_name='Deleted At'), + ), + migrations.AddField( + model_name='exporterhistory', + name='deleted_at', + field=models.DateTimeField(blank=True, null=True, verbose_name='Deleted At'), + ), + migrations.AddField( + model_name='fileasset', + name='deleted_at', + field=models.DateTimeField(blank=True, null=True, verbose_name='Deleted At'), + ), + migrations.AddField( + model_name='githubcommentsync', + name='deleted_at', + field=models.DateTimeField(blank=True, null=True, verbose_name='Deleted At'), + ), + migrations.AddField( + model_name='githubissuesync', + name='deleted_at', + field=models.DateTimeField(blank=True, null=True, verbose_name='Deleted At'), + ), + migrations.AddField( + model_name='githubrepository', + name='deleted_at', + field=models.DateTimeField(blank=True, null=True, verbose_name='Deleted At'), + ), + migrations.AddField( + model_name='githubrepositorysync', + name='deleted_at', + field=models.DateTimeField(blank=True, null=True, verbose_name='Deleted At'), + ), + migrations.AddField( + model_name='globalview', + name='deleted_at', + field=models.DateTimeField(blank=True, null=True, verbose_name='Deleted At'), + ), + migrations.AddField( + model_name='importer', + name='deleted_at', + field=models.DateTimeField(blank=True, null=True, verbose_name='Deleted At'), + ), + migrations.AddField( + model_name='inbox', + name='deleted_at', + field=models.DateTimeField(blank=True, null=True, verbose_name='Deleted At'), + ), + migrations.AddField( + model_name='inboxissue', + name='deleted_at', + field=models.DateTimeField(blank=True, null=True, verbose_name='Deleted At'), + ), + migrations.AddField( + model_name='integration', + name='deleted_at', + field=models.DateTimeField(blank=True, null=True, verbose_name='Deleted At'), + ), + migrations.AddField( + model_name='issue', + name='deleted_at', + field=models.DateTimeField(blank=True, null=True, verbose_name='Deleted At'), + ), + migrations.AddField( + model_name='issueactivity', + name='deleted_at', + field=models.DateTimeField(blank=True, null=True, verbose_name='Deleted At'), + ), + migrations.AddField( + model_name='issueassignee', + name='deleted_at', + field=models.DateTimeField(blank=True, null=True, verbose_name='Deleted At'), + ), + migrations.AddField( + model_name='issueattachment', + name='deleted_at', + field=models.DateTimeField(blank=True, null=True, verbose_name='Deleted At'), + ), + migrations.AddField( + model_name='issueblocker', + name='deleted_at', + field=models.DateTimeField(blank=True, null=True, verbose_name='Deleted At'), + ), + migrations.AddField( + model_name='issuecomment', + name='deleted_at', + field=models.DateTimeField(blank=True, null=True, verbose_name='Deleted At'), + ), + migrations.AddField( + model_name='issuelabel', + name='deleted_at', + field=models.DateTimeField(blank=True, null=True, verbose_name='Deleted At'), + ), + migrations.AddField( + model_name='issuelink', + name='deleted_at', + field=models.DateTimeField(blank=True, null=True, verbose_name='Deleted At'), + ), + migrations.AddField( + model_name='issuemention', + name='deleted_at', + field=models.DateTimeField(blank=True, null=True, verbose_name='Deleted At'), + ), + migrations.AddField( + model_name='issueproperty', + name='deleted_at', + field=models.DateTimeField(blank=True, null=True, verbose_name='Deleted At'), + ), + migrations.AddField( + model_name='issuereaction', + name='deleted_at', + field=models.DateTimeField(blank=True, null=True, verbose_name='Deleted At'), + ), + migrations.AddField( + model_name='issuerelation', + name='deleted_at', + field=models.DateTimeField(blank=True, null=True, verbose_name='Deleted At'), + ), + migrations.AddField( + model_name='issuesequence', + name='deleted_at', + field=models.DateTimeField(blank=True, null=True, verbose_name='Deleted At'), + ), + migrations.AddField( + model_name='issuesubscriber', + name='deleted_at', + field=models.DateTimeField(blank=True, null=True, verbose_name='Deleted At'), + ), + migrations.AddField( + model_name='issueview', + name='deleted_at', + field=models.DateTimeField(blank=True, null=True, verbose_name='Deleted At'), + ), + migrations.AddField( + model_name='issueviewfavorite', + name='deleted_at', + field=models.DateTimeField(blank=True, null=True, verbose_name='Deleted At'), + ), + migrations.AddField( + model_name='issuevote', + name='deleted_at', + field=models.DateTimeField(blank=True, null=True, verbose_name='Deleted At'), + ), + migrations.AddField( + model_name='label', + name='deleted_at', + field=models.DateTimeField(blank=True, null=True, verbose_name='Deleted At'), + ), + migrations.AddField( + model_name='module', + name='deleted_at', + field=models.DateTimeField(blank=True, null=True, verbose_name='Deleted At'), + ), + migrations.AddField( + model_name='modulefavorite', + name='deleted_at', + field=models.DateTimeField(blank=True, null=True, verbose_name='Deleted At'), + ), + migrations.AddField( + model_name='moduleissue', + name='deleted_at', + field=models.DateTimeField(blank=True, null=True, verbose_name='Deleted At'), + ), + migrations.AddField( + model_name='modulelink', + name='deleted_at', + field=models.DateTimeField(blank=True, null=True, verbose_name='Deleted At'), + ), + migrations.AddField( + model_name='modulemember', + name='deleted_at', + field=models.DateTimeField(blank=True, null=True, verbose_name='Deleted At'), + ), + migrations.AddField( + model_name='moduleuserproperties', + name='deleted_at', + field=models.DateTimeField(blank=True, null=True, verbose_name='Deleted At'), + ), + migrations.AddField( + model_name='notification', + name='deleted_at', + field=models.DateTimeField(blank=True, null=True, verbose_name='Deleted At'), + ), + migrations.AddField( + model_name='page', + name='deleted_at', + field=models.DateTimeField(blank=True, null=True, verbose_name='Deleted At'), + ), + migrations.AddField( + model_name='pageblock', + name='deleted_at', + field=models.DateTimeField(blank=True, null=True, verbose_name='Deleted At'), + ), + migrations.AddField( + model_name='pagefavorite', + name='deleted_at', + field=models.DateTimeField(blank=True, null=True, verbose_name='Deleted At'), + ), + migrations.AddField( + model_name='pagelabel', + name='deleted_at', + field=models.DateTimeField(blank=True, null=True, verbose_name='Deleted At'), + ), + migrations.AddField( + model_name='pagelog', + name='deleted_at', + field=models.DateTimeField(blank=True, null=True, verbose_name='Deleted At'), + ), + migrations.AddField( + model_name='project', + name='deleted_at', + field=models.DateTimeField(blank=True, null=True, verbose_name='Deleted At'), + ), + migrations.AddField( + model_name='projectdeployboard', + name='deleted_at', + field=models.DateTimeField(blank=True, null=True, verbose_name='Deleted At'), + ), + migrations.AddField( + model_name='projectfavorite', + name='deleted_at', + field=models.DateTimeField(blank=True, null=True, verbose_name='Deleted At'), + ), + migrations.AddField( + model_name='projectidentifier', + name='deleted_at', + field=models.DateTimeField(blank=True, null=True, verbose_name='Deleted At'), + ), + migrations.AddField( + model_name='projectmember', + name='deleted_at', + field=models.DateTimeField(blank=True, null=True, verbose_name='Deleted At'), + ), + migrations.AddField( + model_name='projectmemberinvite', + name='deleted_at', + field=models.DateTimeField(blank=True, null=True, verbose_name='Deleted At'), + ), + migrations.AddField( + model_name='projectpublicmember', + name='deleted_at', + field=models.DateTimeField(blank=True, null=True, verbose_name='Deleted At'), + ), + migrations.AddField( + model_name='slackprojectsync', + name='deleted_at', + field=models.DateTimeField(blank=True, null=True, verbose_name='Deleted At'), + ), + migrations.AddField( + model_name='socialloginconnection', + name='deleted_at', + field=models.DateTimeField(blank=True, null=True, verbose_name='Deleted At'), + ), + migrations.AddField( + model_name='state', + name='deleted_at', + field=models.DateTimeField(blank=True, null=True, verbose_name='Deleted At'), + ), + migrations.AddField( + model_name='team', + name='deleted_at', + field=models.DateTimeField(blank=True, null=True, verbose_name='Deleted At'), + ), + migrations.AddField( + model_name='teammember', + name='deleted_at', + field=models.DateTimeField(blank=True, null=True, verbose_name='Deleted At'), + ), + migrations.AddField( + model_name='usernotificationpreference', + name='deleted_at', + field=models.DateTimeField(blank=True, null=True, verbose_name='Deleted At'), + ), + migrations.AddField( + model_name='webhook', + name='deleted_at', + field=models.DateTimeField(blank=True, null=True, verbose_name='Deleted At'), + ), + migrations.AddField( + model_name='webhooklog', + name='deleted_at', + field=models.DateTimeField(blank=True, null=True, verbose_name='Deleted At'), + ), + migrations.AddField( + model_name='workspace', + name='deleted_at', + field=models.DateTimeField(blank=True, null=True, verbose_name='Deleted At'), + ), + migrations.AddField( + model_name='workspaceintegration', + name='deleted_at', + field=models.DateTimeField(blank=True, null=True, verbose_name='Deleted At'), + ), + migrations.AddField( + model_name='workspacemember', + name='deleted_at', + field=models.DateTimeField(blank=True, null=True, verbose_name='Deleted At'), + ), + migrations.AddField( + model_name='workspacememberinvite', + name='deleted_at', + field=models.DateTimeField(blank=True, null=True, verbose_name='Deleted At'), + ), + migrations.AddField( + model_name='workspacetheme', + name='deleted_at', + field=models.DateTimeField(blank=True, null=True, verbose_name='Deleted At'), + ), + migrations.AddField( + model_name='workspaceuserproperties', + name='deleted_at', + field=models.DateTimeField(blank=True, null=True, verbose_name='Deleted At'), + ), + ] diff --git a/apiserver/plane/db/mixins.py b/apiserver/plane/db/mixins.py index f1756e5adca..0203eb8ce80 100644 --- a/apiserver/plane/db/mixins.py +++ b/apiserver/plane/db/mixins.py @@ -1,7 +1,9 @@ -# Python imports - # Django imports from django.db import models +from django.utils import timezone + +# Module imports +from plane.bgtasks.deletion_task import soft_delete_related_objects class TimeAuditModel(models.Model): @@ -41,7 +43,45 @@ class Meta: abstract = True -class AuditModel(TimeAuditModel, UserAuditModel): +class SoftDeletionManager(models.Manager): + def get_queryset(self): + return super().get_queryset().filter(deleted_at__isnull=True) + + +class SoftDeleteModel(models.Model): + """To soft delete records""" + + deleted_at = models.DateTimeField( + verbose_name="Deleted At", + null=True, + blank=True, + ) + + objects = SoftDeletionManager() + all_objects = models.Manager() + + class Meta: + abstract = True + + def delete(self, using=None, soft=True, *args, **kwargs): + if soft: + # Soft delete the current instance + self.deleted_at = timezone.now() + self.save(using=using) + + soft_delete_related_objects.delay( + self._meta.app_label, + self._meta.model_name, + self.pk, + using=using, + ) + + else: + # Perform hard delete if soft deletion is not enabled + return super().delete(using=using, *args, **kwargs) + + +class AuditModel(TimeAuditModel, UserAuditModel, SoftDeleteModel): """To path when the record was created and last modified""" class Meta: diff --git a/apiserver/plane/db/models/cycle.py b/apiserver/plane/db/models/cycle.py index 1b4e8e75bb1..57982f9cbed 100644 --- a/apiserver/plane/db/models/cycle.py +++ b/apiserver/plane/db/models/cycle.py @@ -115,6 +115,7 @@ def __str__(self): return f"{self.cycle}" +# DEPRECATED TODO: - Remove in next release class CycleFavorite(ProjectBaseModel): """_summary_ CycleFavorite (model): To store all the cycle favorite of the user diff --git a/apiserver/plane/db/models/issue.py b/apiserver/plane/db/models/issue.py index 7a17853c374..a1ef7225426 100644 --- a/apiserver/plane/db/models/issue.py +++ b/apiserver/plane/db/models/issue.py @@ -91,6 +91,7 @@ def get_queryset(self): | models.Q(issue_inbox__status=2) | models.Q(issue_inbox__isnull=True) ) + .filter(deleted_at__isnull=True) .exclude(archived_at__isnull=False) .exclude(project__archived_at__isnull=False) .exclude(is_draft=True) diff --git a/apiserver/plane/db/models/module.py b/apiserver/plane/db/models/module.py index 7e58088dca4..9b57414d088 100644 --- a/apiserver/plane/db/models/module.py +++ b/apiserver/plane/db/models/module.py @@ -168,6 +168,7 @@ def __str__(self): return f"{self.module.name} {self.url}" +# DEPRECATED TODO: - Remove in next release class ModuleFavorite(ProjectBaseModel): """_summary_ ModuleFavorite (model): To store all the module favorite of the user diff --git a/apiserver/plane/db/models/page.py b/apiserver/plane/db/models/page.py index 3602bce1fa5..18884a7db52 100644 --- a/apiserver/plane/db/models/page.py +++ b/apiserver/plane/db/models/page.py @@ -93,6 +93,7 @@ def __str__(self): return f"{self.page.name} {self.entity_name}" +# DEPRECATED TODO: - Remove in next release class PageBlock(ProjectBaseModel): page = models.ForeignKey( "db.Page", on_delete=models.CASCADE, related_name="blocks" @@ -149,6 +150,7 @@ def __str__(self): return f"{self.page.name} <{self.name}>" +# DEPRECATED TODO: - Remove in next release class PageFavorite(ProjectBaseModel): user = models.ForeignKey( settings.AUTH_USER_MODEL, diff --git a/apiserver/plane/db/models/project.py b/apiserver/plane/db/models/project.py index 49fca1323e8..f81cf6774fe 100644 --- a/apiserver/plane/db/models/project.py +++ b/apiserver/plane/db/models/project.py @@ -227,6 +227,7 @@ class Meta: ordering = ("-created_at",) +# DEPRECATED TODO: - Remove in next release class ProjectFavorite(ProjectBaseModel): user = models.ForeignKey( settings.AUTH_USER_MODEL, diff --git a/apiserver/plane/db/models/view.py b/apiserver/plane/db/models/view.py index 87f0899c355..14b58966592 100644 --- a/apiserver/plane/db/models/view.py +++ b/apiserver/plane/db/models/view.py @@ -51,7 +51,7 @@ def get_default_display_properties(): "updated_on": True, } - +# DEPRECATED TODO: - Remove in next release class GlobalView(BaseModel): workspace = models.ForeignKey( "db.Workspace", on_delete=models.CASCADE, related_name="global_views" @@ -87,7 +87,6 @@ def __str__(self): return f"{self.name} <{self.workspace.name}>" -# DEPRECATED TODO: - Remove in next release class IssueView(WorkspaceBaseModel): name = models.CharField(max_length=255, verbose_name="View Name") description = models.TextField(verbose_name="View Description", blank=True) @@ -113,6 +112,7 @@ def __str__(self): return f"{self.name} <{self.project.name}>" +# DEPRECATED TODO: - Remove in next release class IssueViewFavorite(ProjectBaseModel): user = models.ForeignKey( settings.AUTH_USER_MODEL, diff --git a/apiserver/plane/license/migrations/0002_instance_deleted_at_instanceadmin_deleted_at_and_more.py b/apiserver/plane/license/migrations/0002_instance_deleted_at_instanceadmin_deleted_at_and_more.py new file mode 100644 index 00000000000..3cc643ee836 --- /dev/null +++ b/apiserver/plane/license/migrations/0002_instance_deleted_at_instanceadmin_deleted_at_and_more.py @@ -0,0 +1,28 @@ +# Generated by Django 4.2.11 on 2024-05-07 07:30 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('license', '0001_initial'), + ] + + operations = [ + migrations.AddField( + model_name='instance', + name='deleted_at', + field=models.DateTimeField(blank=True, null=True, verbose_name='Deleted At'), + ), + migrations.AddField( + model_name='instanceadmin', + name='deleted_at', + field=models.DateTimeField(blank=True, null=True, verbose_name='Deleted At'), + ), + migrations.AddField( + model_name='instanceconfiguration', + name='deleted_at', + field=models.DateTimeField(blank=True, null=True, verbose_name='Deleted At'), + ), + ] From fed3aa8540aab37ae05086091071c551b749ba5f Mon Sep 17 00:00:00 2001 From: NarayanBavisetti Date: Fri, 26 Jul 2024 17:23:25 +0530 Subject: [PATCH 2/4] chore: migration files --- ..._at_apiactivitylog_deleted_at_and_more.py} | 49 ++++++++++++++++--- ...eleted_at_instance_deleted_at_and_more.py} | 9 +++- 2 files changed, 49 insertions(+), 9 deletions(-) rename apiserver/plane/db/migrations/{0066_analyticview_deleted_at_apiactivitylog_deleted_at_and_more.py => 0073_analyticview_deleted_at_apiactivitylog_deleted_at_and_more.py} (90%) rename apiserver/plane/license/migrations/{0002_instance_deleted_at_instanceadmin_deleted_at_and_more.py => 0004_changelog_deleted_at_instance_deleted_at_and_more.py} (69%) diff --git a/apiserver/plane/db/migrations/0066_analyticview_deleted_at_apiactivitylog_deleted_at_and_more.py b/apiserver/plane/db/migrations/0073_analyticview_deleted_at_apiactivitylog_deleted_at_and_more.py similarity index 90% rename from apiserver/plane/db/migrations/0066_analyticview_deleted_at_apiactivitylog_deleted_at_and_more.py rename to apiserver/plane/db/migrations/0073_analyticview_deleted_at_apiactivitylog_deleted_at_and_more.py index 82bfe5045e2..ba0e5307edc 100644 --- a/apiserver/plane/db/migrations/0066_analyticview_deleted_at_apiactivitylog_deleted_at_and_more.py +++ b/apiserver/plane/db/migrations/0073_analyticview_deleted_at_apiactivitylog_deleted_at_and_more.py @@ -1,4 +1,4 @@ -# Generated by Django 4.2.11 on 2024-05-13 11:01 +# Generated by Django 4.2.11 on 2024-07-26 11:31 from django.db import migrations, models @@ -6,7 +6,7 @@ class Migration(migrations.Migration): dependencies = [ - ('db', '0065_auto_20240415_0937'), + ('db', '0072_issueattachment_external_id_and_more'), ] operations = [ @@ -60,6 +60,11 @@ class Migration(migrations.Migration): name='deleted_at', field=models.DateTimeField(blank=True, null=True, verbose_name='Deleted At'), ), + migrations.AddField( + model_name='deployboard', + name='deleted_at', + field=models.DateTimeField(blank=True, null=True, verbose_name='Deleted At'), + ), migrations.AddField( model_name='emailnotificationlog', name='deleted_at', @@ -175,11 +180,6 @@ class Migration(migrations.Migration): name='deleted_at', field=models.DateTimeField(blank=True, null=True, verbose_name='Deleted At'), ), - migrations.AddField( - model_name='issueproperty', - name='deleted_at', - field=models.DateTimeField(blank=True, null=True, verbose_name='Deleted At'), - ), migrations.AddField( model_name='issuereaction', name='deleted_at', @@ -200,6 +200,16 @@ class Migration(migrations.Migration): name='deleted_at', field=models.DateTimeField(blank=True, null=True, verbose_name='Deleted At'), ), + migrations.AddField( + model_name='issuetype', + name='deleted_at', + field=models.DateTimeField(blank=True, null=True, verbose_name='Deleted At'), + ), + migrations.AddField( + model_name='issueuserproperty', + name='deleted_at', + field=models.DateTimeField(blank=True, null=True, verbose_name='Deleted At'), + ), migrations.AddField( model_name='issueview', name='deleted_at', @@ -280,6 +290,11 @@ class Migration(migrations.Migration): name='deleted_at', field=models.DateTimeField(blank=True, null=True, verbose_name='Deleted At'), ), + migrations.AddField( + model_name='pageversion', + name='deleted_at', + field=models.DateTimeField(blank=True, null=True, verbose_name='Deleted At'), + ), migrations.AddField( model_name='project', name='deleted_at', @@ -310,6 +325,11 @@ class Migration(migrations.Migration): name='deleted_at', field=models.DateTimeField(blank=True, null=True, verbose_name='Deleted At'), ), + migrations.AddField( + model_name='projectpage', + name='deleted_at', + field=models.DateTimeField(blank=True, null=True, verbose_name='Deleted At'), + ), migrations.AddField( model_name='projectpublicmember', name='deleted_at', @@ -340,11 +360,26 @@ class Migration(migrations.Migration): name='deleted_at', field=models.DateTimeField(blank=True, null=True, verbose_name='Deleted At'), ), + migrations.AddField( + model_name='teampage', + name='deleted_at', + field=models.DateTimeField(blank=True, null=True, verbose_name='Deleted At'), + ), + migrations.AddField( + model_name='userfavorite', + name='deleted_at', + field=models.DateTimeField(blank=True, null=True, verbose_name='Deleted At'), + ), migrations.AddField( model_name='usernotificationpreference', name='deleted_at', field=models.DateTimeField(blank=True, null=True, verbose_name='Deleted At'), ), + migrations.AddField( + model_name='userrecentvisit', + name='deleted_at', + field=models.DateTimeField(blank=True, null=True, verbose_name='Deleted At'), + ), migrations.AddField( model_name='webhook', name='deleted_at', diff --git a/apiserver/plane/license/migrations/0002_instance_deleted_at_instanceadmin_deleted_at_and_more.py b/apiserver/plane/license/migrations/0004_changelog_deleted_at_instance_deleted_at_and_more.py similarity index 69% rename from apiserver/plane/license/migrations/0002_instance_deleted_at_instanceadmin_deleted_at_and_more.py rename to apiserver/plane/license/migrations/0004_changelog_deleted_at_instance_deleted_at_and_more.py index 3cc643ee836..4e238877c33 100644 --- a/apiserver/plane/license/migrations/0002_instance_deleted_at_instanceadmin_deleted_at_and_more.py +++ b/apiserver/plane/license/migrations/0004_changelog_deleted_at_instance_deleted_at_and_more.py @@ -1,4 +1,4 @@ -# Generated by Django 4.2.11 on 2024-05-07 07:30 +# Generated by Django 4.2.11 on 2024-07-26 11:31 from django.db import migrations, models @@ -6,10 +6,15 @@ class Migration(migrations.Migration): dependencies = [ - ('license', '0001_initial'), + ('license', '0003_alter_changelog_title_alter_changelog_version_and_more'), ] operations = [ + migrations.AddField( + model_name='changelog', + name='deleted_at', + field=models.DateTimeField(blank=True, null=True, verbose_name='Deleted At'), + ), migrations.AddField( model_name='instance', name='deleted_at', From 83afaf9c5ae6b4ebf6ef565e53e0005cc2727841 Mon Sep 17 00:00:00 2001 From: NarayanBavisetti Date: Fri, 26 Jul 2024 17:26:53 +0530 Subject: [PATCH 3/4] chore: celery time change --- apiserver/plane/celery.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apiserver/plane/celery.py b/apiserver/plane/celery.py index 36d3942edc2..4d651255683 100644 --- a/apiserver/plane/celery.py +++ b/apiserver/plane/celery.py @@ -38,7 +38,7 @@ }, "check-every-day-to-delete-hard-delete": { "task": "plane.bgtasks.deletion_task.hard_delete", - "schedule": crontab(hour=11, minute=9), + "schedule": crontab(hour=0, minute=0), }, } From 9eab91ec7b88dee666c1ede82909b2b290a3682b Mon Sep 17 00:00:00 2001 From: NarayanBavisetti Date: Fri, 26 Jul 2024 18:04:23 +0530 Subject: [PATCH 4/4] chore: changed the deletion time --- apiserver/plane/bgtasks/deletion_task.py | 65 ++++++++++++++++++++---- 1 file changed, 55 insertions(+), 10 deletions(-) diff --git a/apiserver/plane/bgtasks/deletion_task.py b/apiserver/plane/bgtasks/deletion_task.py index 1306d02c82c..6b155934e9a 100644 --- a/apiserver/plane/bgtasks/deletion_task.py +++ b/apiserver/plane/bgtasks/deletion_task.py @@ -50,51 +50,96 @@ def hard_delete(): IssueView, Label, State, + IssueActivity, + IssueComment, + IssueLink, + IssueReaction, + UserFavorite, + ModuleIssue, + CycleIssue, + Estimate, + EstimatePoint ) # check delete workspace _ = Workspace.all_objects.filter( - deleted_at__lt=timezone.now() - timezone.timedelta(days=0) + deleted_at__lt=timezone.now() - timezone.timedelta(days=30) ).delete() # check delete project _ = Project.all_objects.filter( - deleted_at__lt=timezone.now() - timezone.timedelta(days=0) + deleted_at__lt=timezone.now() - timezone.timedelta(days=30) ).delete() # check delete cycle _ = Cycle.all_objects.filter( - deleted_at__lt=timezone.now() - timezone.timedelta(days=0) + deleted_at__lt=timezone.now() - timezone.timedelta(days=30) ).delete() # check delete module _ = Module.all_objects.filter( - deleted_at__lt=timezone.now() - timezone.timedelta(days=0) + deleted_at__lt=timezone.now() - timezone.timedelta(days=30) ).delete() # check delete issue _ = Issue.all_objects.filter( - deleted_at__lt=timezone.now() - timezone.timedelta(days=0) + deleted_at__lt=timezone.now() - timezone.timedelta(days=30) ).delete() # check delete page _ = Page.all_objects.filter( - deleted_at__lt=timezone.now() - timezone.timedelta(days=0) + deleted_at__lt=timezone.now() - timezone.timedelta(days=30) ).delete() # check delete view _ = IssueView.all_objects.filter( - deleted_at__lt=timezone.now() - timezone.timedelta(days=0) + deleted_at__lt=timezone.now() - timezone.timedelta(days=30) ).delete() # check delete label _ = Label.all_objects.filter( - deleted_at__lt=timezone.now() - timezone.timedelta(days=0) + deleted_at__lt=timezone.now() - timezone.timedelta(days=30) ).delete() # check delete state _ = State.all_objects.filter( - deleted_at__lt=timezone.now() - timezone.timedelta(days=0) + deleted_at__lt=timezone.now() - timezone.timedelta(days=30) + ).delete() + + _ = IssueActivity.all_objects.filter( + deleted_at__lt=timezone.now() - timezone.timedelta(days=30) + ).delete() + + _ = IssueComment.all_objects.filter( + deleted_at__lt=timezone.now() - timezone.timedelta(days=30) + ).delete() + + _ = IssueLink.all_objects.filter( + deleted_at__lt=timezone.now() - timezone.timedelta(days=30) + ).delete() + + _ = IssueReaction.all_objects.filter( + deleted_at__lt=timezone.now() - timezone.timedelta(days=30) + ).delete() + + _ = UserFavorite.all_objects.filter( + deleted_at__lt=timezone.now() - timezone.timedelta(days=30) + ).delete() + + _ = ModuleIssue.all_objects.filter( + deleted_at__lt=timezone.now() - timezone.timedelta(days=30) + ).delete() + + _ = CycleIssue.all_objects.filter( + deleted_at__lt=timezone.now() - timezone.timedelta(days=30) + ).delete() + + _ = Estimate.all_objects.filter( + deleted_at__lt=timezone.now() - timezone.timedelta(days=30) + ).delete() + + _ = EstimatePoint.all_objects.filter( + deleted_at__lt=timezone.now() - timezone.timedelta(days=30) ).delete() # at last, check for every thing which ever is left and delete it @@ -107,7 +152,7 @@ def hard_delete(): 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=0) + deleted_at__lt=timezone.now() - timezone.timedelta(days=30) ).delete() return