From a4727fd2b84501310243824837ffd121897e29f8 Mon Sep 17 00:00:00 2001 From: Prince kumar <68219958+855princekumar@users.noreply.github.com> Date: Thu, 26 Mar 2026 11:23:14 +0530 Subject: [PATCH 01/10] Fix duplicate check creation in auto_create_check_receiver --- openwisp_monitoring/check/base/models.py | 27 ++++++++++++++++-------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/openwisp_monitoring/check/base/models.py b/openwisp_monitoring/check/base/models.py index ed1af76f4..620802df3 100644 --- a/openwisp_monitoring/check/base/models.py +++ b/openwisp_monitoring/check/base/models.py @@ -74,26 +74,20 @@ def clean(self): self.check_instance.validate() def full_clean(self, *args, **kwargs): - # The name of the check will be the same as the - # 'check_type' chosen by the user when the - # name field is empty (useful for CheckInline) if not self.name: self.name = self.get_check_type_display() return super().full_clean(*args, **kwargs) @cached_property def check_class(self): - """Returns the check class.""" return import_string(self.check_type) @cached_property def check_instance(self): - """Returns the check class instance.""" check_class = self.check_class return check_class(check=self, params=self.params) def perform_check(self, store=True): - """Initializes check instance and calls the check method.""" if ( hasattr(self.content_object, "is_deactivated") and self.content_object.is_deactivated() @@ -106,14 +100,16 @@ def perform_check(self, store=True): def perform_check_delayed(self, duration=0): from ..tasks import perform_check - perform_check.apply_async(args=[self.id], countdown=duration) @classmethod - def auto_create_check_receiver(cls, created, **kwargs): + def auto_create_check_receiver(cls, sender, instance, created, **kwargs): if not created: return - transaction_on_commit(lambda: _auto_check_receiver(created=created, **kwargs)) + + transaction_on_commit( + lambda: _auto_check_receiver(sender=sender, instance=instance) + ) def _auto_check_receiver(sender, instance, **kwargs): @@ -121,9 +117,22 @@ def _auto_check_receiver(sender, instance, **kwargs): app_label = sender._meta.app_label object_id = str(instance.pk) + ct = ContentType.objects.get_for_model(instance) + for class_string, name, auto_create_setting in app_settings.CHECK_CLASSES: if not getattr(app_settings, auto_create_setting): continue + + + from openwisp_monitoring.check.models import Check + + if Check.objects.filter( + content_type=ct, + object_id=object_id, + name=name + ).exists(): + continue + auto_create_check.delay( model=model, app_label=app_label, From 50ac4ada68baffcc2e1cb1b459d42fed84363f66 Mon Sep 17 00:00:00 2001 From: Prince kumar <68219958+855princekumar@users.noreply.github.com> Date: Thu, 26 Mar 2026 11:51:36 +0530 Subject: [PATCH 02/10] Move Check import to top-level Moved the import of Check to the top-level for better performance and safety. --- openwisp_monitoring/check/base/models.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/openwisp_monitoring/check/base/models.py b/openwisp_monitoring/check/base/models.py index 620802df3..6433660d7 100644 --- a/openwisp_monitoring/check/base/models.py +++ b/openwisp_monitoring/check/base/models.py @@ -14,6 +14,9 @@ from .. import settings as app_settings from ..tasks import auto_create_check +# ✅ moved import here (outside loop, top-level safe) +from openwisp_monitoring.check.models import Check + class AbstractCheck(TimeStampedEditableModel): name = models.CharField(max_length=64, db_index=True) @@ -122,10 +125,7 @@ def _auto_check_receiver(sender, instance, **kwargs): for class_string, name, auto_create_setting in app_settings.CHECK_CLASSES: if not getattr(app_settings, auto_create_setting): continue - - from openwisp_monitoring.check.models import Check - if Check.objects.filter( content_type=ct, object_id=object_id, From 35c6c4f7967cf8f48573b7e39f8263f54cd13db0 Mon Sep 17 00:00:00 2001 From: Prince kumar <68219958+855princekumar@users.noreply.github.com> Date: Thu, 26 Mar 2026 11:52:40 +0530 Subject: [PATCH 03/10] Refactor import statement for Check model Moved import statement for Check to top-level for safety. --- openwisp_monitoring/check/base/models.py | 1 - 1 file changed, 1 deletion(-) diff --git a/openwisp_monitoring/check/base/models.py b/openwisp_monitoring/check/base/models.py index 6433660d7..ca7318106 100644 --- a/openwisp_monitoring/check/base/models.py +++ b/openwisp_monitoring/check/base/models.py @@ -14,7 +14,6 @@ from .. import settings as app_settings from ..tasks import auto_create_check -# ✅ moved import here (outside loop, top-level safe) from openwisp_monitoring.check.models import Check From 7238130cdc664c14aeb5c73b680bb8a7ef3205f4 Mon Sep 17 00:00:00 2001 From: Prince kumar <68219958+855princekumar@users.noreply.github.com> Date: Thu, 26 Mar 2026 12:25:01 +0530 Subject: [PATCH 04/10] [fix] Fix duplicate check creation in auto_create_check_receiver #649 Moved Check import inside function to avoid circular import issues while keeping it outside the loop for efficiency. Also fixed code style issues to comply with QA checks. Fixes #649 Moved Check import inside function to avoid circular import issues while keeping it outside the loop for efficiency. Also ensured code style compliance. Fixes #649 --- openwisp_monitoring/check/base/models.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/openwisp_monitoring/check/base/models.py b/openwisp_monitoring/check/base/models.py index ca7318106..fc3c14f09 100644 --- a/openwisp_monitoring/check/base/models.py +++ b/openwisp_monitoring/check/base/models.py @@ -14,8 +14,6 @@ from .. import settings as app_settings from ..tasks import auto_create_check -from openwisp_monitoring.check.models import Check - class AbstractCheck(TimeStampedEditableModel): name = models.CharField(max_length=64, db_index=True) @@ -102,6 +100,7 @@ def perform_check(self, store=True): def perform_check_delayed(self, duration=0): from ..tasks import perform_check + perform_check.apply_async(args=[self.id], countdown=duration) @classmethod @@ -121,14 +120,16 @@ def _auto_check_receiver(sender, instance, **kwargs): ct = ContentType.objects.get_for_model(instance) + from openwisp_monitoring.check.models import Check + for class_string, name, auto_create_setting in app_settings.CHECK_CLASSES: if not getattr(app_settings, auto_create_setting): continue - + if Check.objects.filter( content_type=ct, object_id=object_id, - name=name + name=name, ).exists(): continue From e45aa946c53703c9df4eb7980227a0064e46860f Mon Sep 17 00:00:00 2001 From: Prince kumar <68219958+855princekumar@users.noreply.github.com> Date: Thu, 26 Mar 2026 12:49:39 +0530 Subject: [PATCH 05/10] [fix] Fix duplicate check creation in auto_create_check_receiver #649 Ensure idempotency by validating existing checks using check_type instead of name. Fixes #649 --- openwisp_monitoring/check/base/models.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openwisp_monitoring/check/base/models.py b/openwisp_monitoring/check/base/models.py index fc3c14f09..89623bc8b 100644 --- a/openwisp_monitoring/check/base/models.py +++ b/openwisp_monitoring/check/base/models.py @@ -120,6 +120,7 @@ def _auto_check_receiver(sender, instance, **kwargs): ct = ContentType.objects.get_for_model(instance) + # safe import to avoid circular dependency from openwisp_monitoring.check.models import Check for class_string, name, auto_create_setting in app_settings.CHECK_CLASSES: @@ -129,7 +130,7 @@ def _auto_check_receiver(sender, instance, **kwargs): if Check.objects.filter( content_type=ct, object_id=object_id, - name=name, + check_type=class_string, ).exists(): continue From 07a2a1e17f1dad4e7674d334099945b04810c659 Mon Sep 17 00:00:00 2001 From: Prince kumar <68219958+855princekumar@users.noreply.github.com> Date: Thu, 26 Mar 2026 13:13:45 +0530 Subject: [PATCH 06/10] [fix] Fix duplicate check creation in auto_create_check_receiver #649 Ensure idempotent check creation by validating existing checks using check_type. Use swapper.load_model to avoid app registry issues and circular imports. Fixes #649 --- openwisp_monitoring/check/base/models.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/openwisp_monitoring/check/base/models.py b/openwisp_monitoring/check/base/models.py index 89623bc8b..fc1f8c801 100644 --- a/openwisp_monitoring/check/base/models.py +++ b/openwisp_monitoring/check/base/models.py @@ -114,15 +114,16 @@ def auto_create_check_receiver(cls, sender, instance, created, **kwargs): def _auto_check_receiver(sender, instance, **kwargs): + from swapper import load_model + + Check = load_model("check", "Check") + model = sender.__name__.lower() app_label = sender._meta.app_label object_id = str(instance.pk) ct = ContentType.objects.get_for_model(instance) - # safe import to avoid circular dependency - from openwisp_monitoring.check.models import Check - for class_string, name, auto_create_setting in app_settings.CHECK_CLASSES: if not getattr(app_settings, auto_create_setting): continue From 85467b33c50d46c14e06f9e4a8b9ea922dc4e316 Mon Sep 17 00:00:00 2001 From: Prince kumar <68219958+855princekumar@users.noreply.github.com> Date: Thu, 26 Mar 2026 13:38:53 +0530 Subject: [PATCH 07/10] [fix] Fix duplicate check creation in auto_create_check_receiver #649 Ensure idempotent check creation by validating existing checks using check_type. Use swapper.load_model to avoid app registry issues and circular imports. Optimize query by fetching existing check types once. --- openwisp_monitoring/check/base/models.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/openwisp_monitoring/check/base/models.py b/openwisp_monitoring/check/base/models.py index fc1f8c801..2263617c5 100644 --- a/openwisp_monitoring/check/base/models.py +++ b/openwisp_monitoring/check/base/models.py @@ -124,15 +124,18 @@ def _auto_check_receiver(sender, instance, **kwargs): ct = ContentType.objects.get_for_model(instance) + existing_check_types = set( + Check.objects.filter( + content_type=ct, + object_id=object_id, + ).values_list("check_type", flat=True) + ) + for class_string, name, auto_create_setting in app_settings.CHECK_CLASSES: if not getattr(app_settings, auto_create_setting): continue - if Check.objects.filter( - content_type=ct, - object_id=object_id, - check_type=class_string, - ).exists(): + if class_string in existing_check_types: continue auto_create_check.delay( From 707e23335e1ebbb61cd5a5fc0842cfefc943bbc5 Mon Sep 17 00:00:00 2001 From: Prince kumar <68219958+855princekumar@users.noreply.github.com> Date: Thu, 26 Mar 2026 13:58:25 +0530 Subject: [PATCH 08/10] [fix] Fix duplicate check creation in auto_create_check_receiver Ensure idempotent check creation by validating existing checks using check_type. Use swapper.load_model to support swappable models and avoid app registry issues. Optimize duplicate detection by prefetching existing check types. Fixes #649 From d28514dac0487b60933617df4076f050700e65ef Mon Sep 17 00:00:00 2001 From: Prince kumar <68219958+855princekumar@users.noreply.github.com> Date: Thu, 26 Mar 2026 14:37:23 +0530 Subject: [PATCH 09/10] [fix] Fix duplicate check creation in auto_create_check_receiver #649 Ensure idempotent check creation by validating existing checks using check_type. Use swapper.load_model to support swappable models and avoid app registry issues. Optimize duplicate detection by prefetching existing check types. Fixes #649 From 08895f0cde28d7884ea77d66e5533e806c49a376 Mon Sep 17 00:00:00 2001 From: Prince kumar <68219958+855princekumar@users.noreply.github.com> Date: Sun, 12 Apr 2026 02:57:21 +0530 Subject: [PATCH 10/10] [fix] Add rationale comments for idempotent check creation #649 Explain use of swapper.load_model and prefetch-based deduplication logic. Fixes #649 --- openwisp_monitoring/check/base/models.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/openwisp_monitoring/check/base/models.py b/openwisp_monitoring/check/base/models.py index 2263617c5..f7ffbaef6 100644 --- a/openwisp_monitoring/check/base/models.py +++ b/openwisp_monitoring/check/base/models.py @@ -124,6 +124,8 @@ def _auto_check_receiver(sender, instance, **kwargs): ct = ContentType.objects.get_for_model(instance) + # Prefetch existing check_types for this object to ensure idempotency + # This avoids creating duplicate checks when the signal is triggered multiple times existing_check_types = set( Check.objects.filter( content_type=ct,