Skip to content

Commit dc7b4b4

Browse files
Process deleting projects as background task
1. Fix Default Filtering in ProjectFilterSet: - Modified the `ProjectFilterSet` to include `is_marked_for_deletion` in the default filtering when `is_archived` is not provided. - This ensures that projects marked for deletion are excluded from default views. 2. Migration for `is_marked_for_deletion`: - Added a migration (`0052_project_is_marked_for_deletion.py`) to introduce the `is_marked_for_deletion` field in the `Project` model. 3. Updated `Project` Model: - Added `is_marked_for_deletion` field to the `Project` model with a default value of `False`. 4. Modified `ProjectListView` to Exclude Marked Projects: - Modified the `get_queryset` method in `ProjectListView` to only fetch projects with `is_marked_for_deletion=False`. 5. Introduced `mark_for_deletion` Method: - Added a `mark_for_deletion` method in the `Project` model to set the `is_marked_for_deletion` flag and save the model. 6. Add `delete_in_background` Method in `Project` Model: - Introduced a new `delete_in_background` method that marks the project for deletion and enqueues a background deletion task. 7. Background Deletion Task: - Created a background deletion task using `django_rq` to handle project deletion asynchronously. - Checks if the project is still marked for deletion before proceeding. - If an error occurs during deletion, updates project status and logs an error. 8. Updated `ProjectActionView` Success Message: - Modified the success message in `ProjectActionView` for the "delete" action to provide a more informative message based on the count. Fixes: #1002 Signed-off-by: Jayanth Kumar <[email protected]>
1 parent 71f33c7 commit dc7b4b4

File tree

6 files changed

+62
-7
lines changed

6 files changed

+62
-7
lines changed

scanpipe/filters.py

+6-2
Original file line numberDiff line numberDiff line change
@@ -349,9 +349,13 @@ def __init__(self, data=None, *args, **kwargs):
349349

350350
# Default filtering by "Active" projects.
351351
if not data or data.get("is_archived", "") == "":
352-
self.queryset = self.queryset.filter(is_archived=False)
352+
self.queryset = self.queryset.filter(
353+
is_archived=False, is_marked_for_deletion=False
354+
)
353355

354-
active_count = Project.objects.filter(is_archived=False).count()
356+
active_count = Project.objects.filter(
357+
is_archived=False, is_marked_for_deletion=False
358+
).count()
355359
archived_count = Project.objects.filter(is_archived=True).count()
356360
self.filters["is_archived"].extra["widget"] = BulmaLinkWidget(
357361
choices=[
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# Generated by Django 5.0.1 on 2024-01-26 12:25
2+
3+
from django.db import migrations, models
4+
5+
6+
class Migration(migrations.Migration):
7+
8+
dependencies = [
9+
('scanpipe', '0051_rename_pipelines_data'),
10+
]
11+
12+
operations = [
13+
migrations.AddField(
14+
model_name='project',
15+
name='is_marked_for_deletion',
16+
field=models.BooleanField(default=False),
17+
),
18+
]

scanpipe/models.py

+12
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@
7676
from packageurl.contrib.django.models import PackageURLMixin
7777
from packageurl.contrib.django.models import PackageURLQuerySetMixin
7878
from rest_framework.authtoken.models import Token
79+
from rq import Queue
7980
from rq.command import send_stop_job_command
8081
from rq.exceptions import NoSuchJobError
8182
from rq.job import Job
@@ -534,6 +535,7 @@ class Project(UUIDPKModel, ExtraDataFieldMixin, UpdateMixin, models.Model):
534535
labels = TaggableManager(through=UUIDTaggedItem)
535536

536537
objects = ProjectQuerySet.as_manager()
538+
is_marked_for_deletion = models.BooleanField(default=False)
537539

538540
class Meta:
539541
ordering = ["-created_date"]
@@ -633,6 +635,16 @@ def delete(self, *args, **kwargs):
633635

634636
return super().delete(*args, **kwargs)
635637

638+
def mark_for_deletion(self):
639+
self.is_marked_for_deletion = True
640+
self.save()
641+
642+
def delete_in_background(self):
643+
# Mark the project for deletion and enqueue background deletion task
644+
self.mark_for_deletion()
645+
q = Queue("default", connection=redis.Redis())
646+
job = q.enqueue(tasks.background_delete_task, self)
647+
636648
def reset(self, keep_input=True):
637649
"""
638650
Reset the project by deleting all related database objects and all work

scanpipe/tasks.py

+18
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@
2424

2525
from django.apps import apps
2626

27+
from django_rq import job
28+
2729
logger = logging.getLogger(__name__)
2830

2931

@@ -76,3 +78,19 @@ def execute_pipeline_task(run_pk):
7678
project.clear_tmp_directory()
7779
if next_run := project.get_next_run():
7880
next_run.start()
81+
82+
83+
@job
84+
def background_delete_task(project):
85+
# Check if the project is still marked for deletion
86+
if not project.is_marked_for_deletion:
87+
return
88+
89+
# Perform the deletion process
90+
try:
91+
project.delete()
92+
except Exception as e:
93+
# Handle errors and update project errors or display a banner
94+
project.is_marked_for_deletion = False
95+
project.save()
96+
project.add_error(description=f"Deletion failed: {str(e)}")

scanpipe/tests/test_views.py

+2-4
Original file line numberDiff line numberDiff line change
@@ -183,11 +183,9 @@ def test_scanpipe_views_project_actions_view(self):
183183
}
184184
response = self.client.post(url, data=data, follow=True)
185185
self.assertRedirects(response, reverse("project_list"))
186-
expected = '<div class="message-body">1 projects have been delete.</div>'
186+
expected = '<div class="message-body">1 project is being deleted in the background.</div>'
187187
self.assertContains(response, expected, html=True)
188-
expected = (
189-
f'<div class="message-body">Project {random_uuid} does not exist.</div>'
190-
)
188+
expected = f"1 project is being deleted in the background."
191189
self.assertContains(response, expected, html=True)
192190

193191
def test_scanpipe_views_project_details_is_archived(self):

scanpipe/views.py

+6-1
Original file line numberDiff line numberDiff line change
@@ -1076,7 +1076,10 @@ def perform_action(self, action, project_uuid, action_kwargs=None):
10761076

10771077
try:
10781078
project = Project.objects.get(pk=project_uuid)
1079-
getattr(project, action)(**action_kwargs)
1079+
if action == "delete":
1080+
project.delete_in_background()
1081+
else:
1082+
getattr(project, action)(**action_kwargs)
10801083
return True
10811084
except Project.DoesNotExist:
10821085
messages.error(self.request, f"Project {project_uuid} does not exist.")
@@ -1086,6 +1089,8 @@ def perform_action(self, action, project_uuid, action_kwargs=None):
10861089
raise Http404
10871090

10881091
def get_success_message(self, action, count):
1092+
if action == "delete":
1093+
return f"{count} project{'s' if count != 1 else ''} {'is' if count == 1 else 'are'} being deleted in the background."
10891094
return f"{count} projects have been {action}."
10901095

10911096

0 commit comments

Comments
 (0)