Skip to content

Commit 9ac921a

Browse files
committed
Add options to the Project reset action
Signed-off-by: tdruez <[email protected]>
1 parent 790e1ef commit 9ac921a

File tree

5 files changed

+133
-52
lines changed

5 files changed

+133
-52
lines changed

scanpipe/forms.py

+26-1
Original file line numberDiff line numberDiff line change
@@ -252,7 +252,7 @@ class BaseProjectActionForm(forms.Form):
252252
)
253253

254254

255-
class ArchiveProjectForm(BaseProjectActionForm):
255+
class ProjectArchiveForm(BaseProjectActionForm):
256256
remove_input = forms.BooleanField(
257257
label="Remove inputs",
258258
initial=True,
@@ -277,6 +277,31 @@ def get_action_kwargs(self):
277277
}
278278

279279

280+
class ProjectResetForm(BaseProjectActionForm):
281+
keep_input = forms.BooleanField(
282+
label="Keep inputs",
283+
initial=True,
284+
required=False,
285+
)
286+
restore_pipelines = forms.BooleanField(
287+
label="Restore existing pipelines",
288+
initial=False,
289+
required=False,
290+
)
291+
execute_now = forms.BooleanField(
292+
label="Execute restored pipeline(s) now",
293+
initial=False,
294+
required=False,
295+
)
296+
297+
def get_action_kwargs(self):
298+
return {
299+
"keep_input": self.cleaned_data["keep_input"],
300+
"restore_pipelines": self.cleaned_data["restore_pipelines"],
301+
"execute_now": self.cleaned_data["execute_now"],
302+
}
303+
304+
280305
class ProjectOutputDownloadForm(BaseProjectActionForm):
281306
output_format = forms.ChoiceField(
282307
label="Choose the output format to include in the ZIP file",

scanpipe/models.py

+20-6
Original file line numberDiff line numberDiff line change
@@ -637,10 +637,9 @@ def archive(self, remove_input=False, remove_codebase=False, remove_output=False
637637
shutil.rmtree(self.tmp_path, ignore_errors=True)
638638
self.setup_work_directory()
639639

640-
self.is_archived = True
641-
self.save(update_fields=["is_archived"])
640+
self.update(is_archived=True)
642641

643-
def delete_related_objects(self, keep_input=False):
642+
def delete_related_objects(self, keep_input=False, keep_labels=False):
644643
"""
645644
Delete all related object instances using the private `_raw_delete` model API.
646645
This bypass the objects collection, cascade deletions, and signals.
@@ -657,7 +656,8 @@ def delete_related_objects(self, keep_input=False):
657656
_, deleted_counter = self.discoveredpackages.all().delete()
658657

659658
# Removes all tags from this project by deleting the UUIDTaggedItem instances.
660-
self.labels.clear()
659+
if not keep_labels:
660+
self.labels.clear()
661661

662662
relationships = [
663663
self.webhookdeliveries,
@@ -690,14 +690,25 @@ def delete(self, *args, **kwargs):
690690

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

693-
def reset(self, keep_input=True):
693+
def reset(self, keep_input=True, restore_pipelines=False, execute_now=False):
694694
"""
695695
Reset the project by deleting all related database objects and all work
696696
directories except the input directory—when the `keep_input` option is True.
697697
"""
698698
self._raise_if_run_in_progress()
699699

700-
self.delete_related_objects(keep_input=keep_input)
700+
restore_pipeline_kwargs = []
701+
if restore_pipelines:
702+
restore_pipeline_kwargs = [
703+
{
704+
"pipeline_name": run.pipeline_name,
705+
"execute_now": execute_now,
706+
"selected_groups": run.selected_groups,
707+
}
708+
for run in self.runs.all()
709+
]
710+
711+
self.delete_related_objects(keep_input=keep_input, keep_labels=True)
701712

702713
work_directories = [
703714
self.codebase_path,
@@ -717,6 +728,9 @@ def reset(self, keep_input=True):
717728

718729
self.setup_work_directory()
719730

731+
for pipeline_kwargs in restore_pipeline_kwargs:
732+
self.add_pipeline(**pipeline_kwargs)
733+
720734
def clone(
721735
self,
722736
clone_name,

scanpipe/templates/scanpipe/modals/project_reset_modal.html

+29-11
Original file line numberDiff line numberDiff line change
@@ -5,18 +5,36 @@
55
<p class="modal-card-title">Reset this project, are you sure?</p>
66
<button class="delete" aria-label="close"></button>
77
</header>
8-
<section class="modal-card-body">
9-
<div class="notification is-danger has-text-weight-semibold">
10-
This action cannot be undone.
11-
</div>
12-
<p class="mb-2">
13-
This action will <strong>delete all related database entries and all data on disks</strong> except for the input/ directory.
14-
</p>
15-
<p class="mb-5">
16-
Are you sure you want to do this?
17-
</p>
18-
</section>
198
<form action="{% url 'project_reset' project.slug %}" method="post">{% csrf_token %}
9+
<section class="modal-card-body">
10+
<div class="notification is-danger has-text-weight-semibold">
11+
This action cannot be undone.
12+
</div>
13+
<p class="mb-2">
14+
This action will <strong>delete all related database entries and all data on disks</strong> except for the input/ directory.
15+
</p>
16+
<p class="mb-5">
17+
Are you sure you want to do this?
18+
</p>
19+
<div class="field">
20+
<label class="label">
21+
{{ reset_form.keep_input }}
22+
{{ reset_form.keep_input.label }}
23+
</label>
24+
</div>
25+
<div class="field">
26+
<label class="label">
27+
{{ reset_form.restore_pipelines }}
28+
{{ reset_form.restore_pipelines.label }}
29+
</label>
30+
</div>
31+
<div class="field">
32+
<label class="label">
33+
{{ reset_form.execute_now }}
34+
{{ reset_form.execute_now.label }}
35+
</label>
36+
</div>
37+
</section>
2038
<footer class="modal-card-foot is-flex is-justify-content-space-between">
2139
<button class="button has-text-weight-semibold" type="reset">No, Cancel</button>
2240
<button class="button is-danger is-no-close" type="submit">Yes, Reset Project</button>

scanpipe/templates/scanpipe/modals/projects_reset_modal.html

+41-23
Original file line numberDiff line numberDiff line change
@@ -6,30 +6,48 @@
66
<p class="modal-card-title">Reset selected projects, are you sure?</p>
77
<button class="delete" aria-label="close"></button>
88
</header>
9-
<section class="modal-card-body">
10-
<div class="notification is-danger has-text-weight-semibold">
11-
This action cannot be undone.
12-
</div>
13-
<p class="mb-2">
14-
This action will <strong>delete all related database entries and all data on disks</strong> except for the input/ directory.
15-
</p>
16-
<p class="mb-5">
17-
Are you sure you want to do this?
18-
</p>
19-
{% if page_obj.paginator.num_pages > 1 %}
20-
<div class="show-on-all-checked">
21-
<hr>
22-
<div class="field include-all-field">
23-
<label class="checkbox" for="{{ archive_form.select_across.id_for_label }}">
24-
<input type="checkbox" name="{{ archive_form.select_across.name }}" id="{{ archive_form.select_across.id_for_label }}">
25-
Include all {{ paginator.count|intcomma }} projects
26-
</label>
27-
<p class="help">{{ outputs_download_form.select_across.help_text }}</p>
28-
</div>
29-
</div>
30-
{% endif %}
31-
</section>
329
<form action="{% url 'project_action' %}" method="post" id="reset-projects-form">{% csrf_token %}
10+
<section class="modal-card-body">
11+
<div class="notification is-danger has-text-weight-semibold">
12+
This action cannot be undone.
13+
</div>
14+
<p class="mb-2">
15+
This action will <strong>delete all related database entries and all data on disks</strong> except for the input/ directory.
16+
</p>
17+
<p class="mb-5">
18+
Are you sure you want to do this?
19+
</p>
20+
<div class="field">
21+
<label class="label">
22+
{{ reset_form.keep_input }}
23+
{{ reset_form.keep_input.label }}
24+
</label>
25+
</div>
26+
<div class="field">
27+
<label class="label">
28+
{{ reset_form.restore_pipelines }}
29+
{{ reset_form.restore_pipelines.label }}
30+
</label>
31+
</div>
32+
<div class="field">
33+
<label class="label">
34+
{{ reset_form.execute_now }}
35+
{{ reset_form.execute_now.label }}
36+
</label>
37+
</div>
38+
{% if page_obj.paginator.num_pages > 1 %}
39+
<div class="show-on-all-checked">
40+
<hr>
41+
<div class="field include-all-field">
42+
<label class="checkbox" for="{{ reset_form.select_across.id_for_label }}">
43+
<input type="checkbox" name="{{ reset_form.select_across.name }}" id="{{ reset_form.select_across.id_for_label }}">
44+
Include all {{ paginator.count|intcomma }} projects
45+
</label>
46+
<p class="help">{{ outputs_download_form.select_across.help_text }}</p>
47+
</div>
48+
</div>
49+
{% endif %}
50+
</section>
3351
<input type="hidden" name="{{ action_form.url_query.name }}" value="{{ request.GET.urlencode }}">
3452
<input type="hidden" name="action" value="reset">
3553
<footer class="modal-card-foot is-flex is-justify-content-space-between">

scanpipe/views.py

+17-11
Original file line numberDiff line numberDiff line change
@@ -76,10 +76,11 @@
7676
from scanpipe.forms import AddInputsForm
7777
from scanpipe.forms import AddLabelsForm
7878
from scanpipe.forms import AddPipelineForm
79-
from scanpipe.forms import ArchiveProjectForm
8079
from scanpipe.forms import BaseProjectActionForm
8180
from scanpipe.forms import EditInputSourceTagForm
8281
from scanpipe.forms import PipelineRunStepSelectionForm
82+
from scanpipe.forms import ProjectArchiveForm
83+
from scanpipe.forms import ProjectResetForm
8384
from scanpipe.forms import ProjectCloneForm
8485
from scanpipe.forms import ProjectForm
8586
from scanpipe.forms import ProjectOutputDownloadForm
@@ -599,7 +600,8 @@ class ProjectListView(
599600
def get_context_data(self, **kwargs):
600601
context = super().get_context_data(**kwargs)
601602
context["action_form"] = BaseProjectActionForm()
602-
context["archive_form"] = ArchiveProjectForm()
603+
context["archive_form"] = ProjectArchiveForm()
604+
context["reset_form"] = ProjectResetForm()
603605
context["outputs_download_form"] = ProjectOutputDownloadForm()
604606
context["report_form"] = ProjectReportForm()
605607
return context
@@ -856,7 +858,8 @@ def get(self, request, *args, **kwargs):
856858
def get_context_data(self, **kwargs):
857859
context = super().get_context_data(**kwargs)
858860
project = self.get_object()
859-
context["archive_form"] = ArchiveProjectForm()
861+
context["archive_form"] = ProjectArchiveForm()
862+
context["reset_form"] = ProjectResetForm()
860863
context["webhook_subscriptions"] = project.webhooksubscriptions.all()
861864
return context
862865

@@ -1136,7 +1139,7 @@ def get_context_data(self, **kwargs):
11361139
class ProjectArchiveView(ConditionalLoginRequired, SingleObjectMixin, FormView):
11371140
model = Project
11381141
http_method_names = ["post"]
1139-
form_class = ArchiveProjectForm
1142+
form_class = ProjectArchiveForm
11401143
success_url = reverse_lazy("project_list")
11411144
success_message = 'The project "{}" has been archived.'
11421145

@@ -1154,20 +1157,22 @@ def form_valid(self, form):
11541157
return response
11551158

11561159

1157-
class ProjectResetView(ConditionalLoginRequired, generic.DeleteView):
1160+
class ProjectResetView(ConditionalLoginRequired, SingleObjectMixin, FormView):
11581161
model = Project
1159-
success_message = 'All data, except inputs, for the "{}" project have been removed.'
1162+
http_method_names = ["post"]
1163+
form_class = ProjectResetForm
1164+
success_url = reverse_lazy("project_list")
1165+
success_message = 'The project "{}" has been reset.'
11601166

11611167
def form_valid(self, form):
11621168
"""Call the reset() method on the project."""
11631169
project = self.get_object()
11641170
try:
1165-
project.reset(keep_input=True)
1171+
project.reset(**form.get_action_kwargs())
11661172
except RunInProgressError as error:
11671173
messages.error(self.request, error)
1168-
else:
1169-
messages.success(self.request, self.success_message.format(project.name))
11701174

1175+
messages.success(self.request, self.success_message.format(project.name))
11711176
return redirect(project)
11721177

11731178

@@ -1195,7 +1200,8 @@ class ProjectActionView(ConditionalLoginRequired, generic.ListView):
11951200
model = Project
11961201
allowed_actions = ["archive", "delete", "reset", "report", "download"]
11971202
action_to_form_class = {
1198-
"archive": ArchiveProjectForm,
1203+
"archive": ProjectArchiveForm,
1204+
"reset": ProjectResetForm,
11991205
"report": ProjectReportForm,
12001206
"download": ProjectOutputDownloadForm,
12011207
}
@@ -1222,7 +1228,7 @@ def post(self, request, *args, **kwargs):
12221228
if action == "report":
12231229
return self.xlsx_report_response(project_qs, action_form)
12241230

1225-
if action == "archive":
1231+
if action in ["archive", "reset"]:
12261232
action_kwargs = action_form.get_action_kwargs()
12271233

12281234
count = 0

0 commit comments

Comments
 (0)