From a6fc0e398419ddb86badd56aad982f9806b30f1d Mon Sep 17 00:00:00 2001 From: tdruez Date: Fri, 21 Nov 2025 19:53:18 +0400 Subject: [PATCH 1/3] Keep webhook subscription in project reset Signed-off-by: tdruez --- scanpipe/api/serializers.py | 6 ++++++ scanpipe/forms.py | 6 ++++++ scanpipe/models.py | 20 +++++++++++++++---- .../scanpipe/modals/project_reset_modal.html | 6 ++++++ 4 files changed, 34 insertions(+), 4 deletions(-) diff --git a/scanpipe/api/serializers.py b/scanpipe/api/serializers.py index 8f8b7a59d7..587a6b411e 100644 --- a/scanpipe/api/serializers.py +++ b/scanpipe/api/serializers.py @@ -582,6 +582,12 @@ class ProjectResetSerializer(serializers.Serializer): initial=True, help_text="Keep the input directory and input sources when resetting.", ) + keep_webhook = serializers.BooleanField( + required=False, + default=True, + initial=True, + help_text="Keep webhook subscriptions when resetting.", + ) restore_pipelines = serializers.BooleanField( required=False, default=False, diff --git a/scanpipe/forms.py b/scanpipe/forms.py index cd40b48ee5..e3f93e3af4 100644 --- a/scanpipe/forms.py +++ b/scanpipe/forms.py @@ -287,6 +287,11 @@ class ProjectResetForm(BaseProjectActionForm): initial=True, required=False, ) + keep_webhook = forms.BooleanField( + label="Keep webhook subscriptions", + initial=True, + required=False, + ) restore_pipelines = forms.BooleanField( label="Restore existing pipelines", initial=False, @@ -301,6 +306,7 @@ class ProjectResetForm(BaseProjectActionForm): def get_action_kwargs(self): return { "keep_input": self.cleaned_data["keep_input"], + "keep_webhook": self.cleaned_data["keep_webhook"], "restore_pipelines": self.cleaned_data["restore_pipelines"], "execute_now": self.cleaned_data["execute_now"], } diff --git a/scanpipe/models.py b/scanpipe/models.py index f12d079957..1204e7dbb4 100644 --- a/scanpipe/models.py +++ b/scanpipe/models.py @@ -678,7 +678,9 @@ def archive(self, remove_input=False, remove_codebase=False, remove_output=False self.update(is_archived=True) - def delete_related_objects(self, keep_input=False, keep_labels=False): + def delete_related_objects( + self, keep_input=False, keep_labels=False, keep_webhook=False + ): """ Delete all related object instances using the private `_raw_delete` model API. This bypass the objects collection, cascade deletions, and signals. @@ -700,7 +702,6 @@ def delete_related_objects(self, keep_input=False, keep_labels=False): relationships = [ self.webhookdeliveries, - self.webhooksubscriptions, self.projectmessages, self.codebaserelations, self.discovereddependencies, @@ -712,6 +713,9 @@ def delete_related_objects(self, keep_input=False, keep_labels=False): if not keep_input: relationships.append(self.inputsources) + if not keep_webhook: + relationships.append(self.webhooksubscriptions) + for qs in relationships: count = qs.all()._raw_delete(qs.db) deleted_counter[qs.model._meta.label] = count @@ -730,7 +734,13 @@ def delete(self, *args, **kwargs): return super().delete(*args, **kwargs) - def reset(self, keep_input=True, restore_pipelines=False, execute_now=False): + def reset( + self, + keep_input=True, + keep_webhook=True, + restore_pipelines=False, + execute_now=False, + ): """ Reset the project by deleting all related database objects and all work directories except the input directory—when the `keep_input` option is True. @@ -748,7 +758,9 @@ def reset(self, keep_input=True, restore_pipelines=False, execute_now=False): for run in self.runs.all() ] - self.delete_related_objects(keep_input=keep_input, keep_labels=True) + self.delete_related_objects( + keep_input=keep_input, keep_labels=True, keep_webhook=keep_webhook + ) work_directories = [ self.codebase_path, diff --git a/scanpipe/templates/scanpipe/modals/project_reset_modal.html b/scanpipe/templates/scanpipe/modals/project_reset_modal.html index 43d07184e0..ade682ca12 100644 --- a/scanpipe/templates/scanpipe/modals/project_reset_modal.html +++ b/scanpipe/templates/scanpipe/modals/project_reset_modal.html @@ -21,6 +21,12 @@ {{ reset_form.keep_input }} {{ reset_form.keep_input.label }} + +
+