Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions scanpipe/filters.py
Original file line number Diff line number Diff line change
Expand Up @@ -349,9 +349,11 @@ def __init__(self, data=None, *args, **kwargs):

# Default filtering by "Active" projects.
if not data or data.get("is_archived", "") == "":
self.queryset = self.queryset.filter(is_archived=False)
self.queryset = self.queryset.active()

active_count = Project.objects.filter(is_archived=False).count()
active_count = Project.objects.filter(
is_archived=False, is_marked_for_deletion=False
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should use active() here as well, no?

).count()
archived_count = Project.objects.filter(is_archived=True).count()
self.filters["is_archived"].extra["widget"] = BulmaLinkWidget(
choices=[
Expand Down
18 changes: 18 additions & 0 deletions scanpipe/migrations/0052_project_is_marked_for_deletion.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Generated by Django 5.0.1 on 2024-01-26 12:25

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('scanpipe', '0051_rename_pipelines_data'),
]

operations = [
migrations.AddField(
model_name='project',
name='is_marked_for_deletion',
field=models.BooleanField(default=False),
),
]
13 changes: 13 additions & 0 deletions scanpipe/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@
from packageurl.contrib.django.models import PackageURLMixin
from packageurl.contrib.django.models import PackageURLQuerySetMixin
from rest_framework.authtoken.models import Token
from rq import Queue
from rq.command import send_stop_job_command
from rq.exceptions import NoSuchJobError
from rq.job import Job
Expand Down Expand Up @@ -488,6 +489,9 @@ def with_counts(self, *fields):

return self.annotate(**annotations)

def active(self):
return self.filter(is_archived=False, is_marked_for_deletion=False)


class UUIDTaggedItem(GenericUUIDTaggedItemBase, TaggedItemBase):
class Meta:
Expand Down Expand Up @@ -531,6 +535,7 @@ class Project(UUIDPKModel, ExtraDataFieldMixin, UpdateMixin, models.Model):
)
notes = models.TextField(blank=True)
settings = models.JSONField(default=dict, blank=True)
is_marked_for_deletion = models.BooleanField(default=False)
labels = TaggableManager(through=UUIDTaggedItem)

objects = ProjectQuerySet.as_manager()
Expand Down Expand Up @@ -633,6 +638,14 @@ def delete(self, *args, **kwargs):

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

def mark_for_deletion(self):
self.update(is_marked_for_deletion=True)

def delete_in_background(self):
# Mark the project for deletion and enqueue background deletion task
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A docstring would be better than a comment.

self.mark_for_deletion()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can put self.update(is_marked_for_deletion=True) directly here, no need for the mark_for_deletion method yet since it's a one-liner that is only used in one place.

django_rq.enqueue(tasks.background_delete_task, self)

def reset(self, keep_input=True):
"""
Reset the project by deleting all related database objects and all work
Expand Down
16 changes: 16 additions & 0 deletions scanpipe/tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@

from django.apps import apps

from django_rq import job

logger = logging.getLogger(__name__)


Expand Down Expand Up @@ -76,3 +78,17 @@ def execute_pipeline_task(run_pk):
project.clear_tmp_directory()
if next_run := project.get_next_run():
next_run.start()


@job
def background_delete_task(project):
# Check if the project is still marked for deletion
if not project.is_marked_for_deletion:
return

try:
project.delete()
except Exception as e:
info(f"Deletion failed: {str(e)}", project.pk)
project.update(is_marked_for_deletion=True)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm wondering is this line is of any use, the is_marked_for_deletion must be already True to reach this line, no?

project.add_error(description=f"Deletion failed: {str(e)}")
4 changes: 1 addition & 3 deletions scanpipe/tests/test_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -185,9 +185,7 @@ def test_scanpipe_views_project_actions_view(self):
self.assertRedirects(response, reverse("project_list"))
expected = '<div class="message-body">1 projects have been delete.</div>'
self.assertContains(response, expected, html=True)
expected = (
f'<div class="message-body">Project {random_uuid} does not exist.</div>'
)
expected = f"1 projects have been delete."
self.assertContains(response, expected, html=True)

def test_scanpipe_views_project_details_is_archived(self):
Expand Down
5 changes: 4 additions & 1 deletion scanpipe/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -1076,7 +1076,10 @@ def perform_action(self, action, project_uuid, action_kwargs=None):

try:
project = Project.objects.get(pk=project_uuid)
getattr(project, action)(**action_kwargs)
if action == "delete":
project.delete_in_background()
else:
getattr(project, action)(**action_kwargs)
return True
except Project.DoesNotExist:
messages.error(self.request, f"Project {project_uuid} does not exist.")
Expand Down