From e102c996fd2cd6551925be9a2f7547294a313c90 Mon Sep 17 00:00:00 2001 From: Arthur Date: Tue, 12 Aug 2025 13:48:08 -0700 Subject: [PATCH 1/5] NPL-373 fix changelog registration --- netbox_custom_objects/__init__.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/netbox_custom_objects/__init__.py b/netbox_custom_objects/__init__.py index dc4cdc1..01f7972 100644 --- a/netbox_custom_objects/__init__.py +++ b/netbox_custom_objects/__init__.py @@ -106,11 +106,9 @@ def get_models(self, include_auto_created=False, include_swapped=False): custom_object_types = CustomObjectType.objects.all() for custom_type in custom_object_types: - # Only yield already cached models during discovery - if CustomObjectType.is_model_cached(custom_type.id): - model = CustomObjectType.get_cached_model(custom_type.id) - if model: - yield model + model = custom_type.get_model() + if model: + yield model config = CustomObjectsPluginConfig From d029321a8a7b6b5431394a22f59984d3ac4b2c08 Mon Sep 17 00:00:00 2001 From: Arthur Date: Tue, 12 Aug 2025 14:04:40 -0700 Subject: [PATCH 2/5] NPL-373 fix changelog registration --- netbox_custom_objects/__init__.py | 59 ++++++++++++++++++------------- 1 file changed, 34 insertions(+), 25 deletions(-) diff --git a/netbox_custom_objects/__init__.py b/netbox_custom_objects/__init__.py index 01f7972..bb7c08a 100644 --- a/netbox_custom_objects/__init__.py +++ b/netbox_custom_objects/__init__.py @@ -49,6 +49,7 @@ class CustomObjectsPluginConfig(PluginConfig): default_settings = {} required_settings = [] template_extensions = "template_content.template_extensions" + _in_get_models = False # Recursion guard def get_model(self, model_name, require_ready=True): try: @@ -84,31 +85,39 @@ def get_model(self, model_name, require_ready=True): def get_models(self, include_auto_created=False, include_swapped=False): """Return all models for this plugin, including custom object type models.""" - # Get the regular Django models first - for model in super().get_models(include_auto_created, include_swapped): - yield model - - # Suppress warnings about database calls during model loading - with warnings.catch_warnings(): - warnings.filterwarnings( - "ignore", category=RuntimeWarning, message=".*database.*" - ) - warnings.filterwarnings( - "ignore", category=UserWarning, message=".*database.*" - ) - - # Skip custom object type model loading if running during migration - if is_running_migration() or not check_custom_object_type_table_exists(): - return - - # Add custom object type models - from .models import CustomObjectType - - custom_object_types = CustomObjectType.objects.all() - for custom_type in custom_object_types: - model = custom_type.get_model() - if model: - yield model + # Prevent recursion + if self._in_get_models: + return + + self._in_get_models = True + try: + # Get the regular Django models first + for model in super().get_models(include_auto_created, include_swapped): + yield model + + # Suppress warnings about database calls during model loading + with warnings.catch_warnings(): + warnings.filterwarnings( + "ignore", category=RuntimeWarning, message=".*database.*" + ) + warnings.filterwarnings( + "ignore", category=UserWarning, message=".*database.*" + ) + + # Skip custom object type model loading if running during migration + if is_running_migration() or not check_custom_object_type_table_exists(): + return + + # Add custom object type models + from .models import CustomObjectType + + custom_object_types = CustomObjectType.objects.all() + for custom_type in custom_object_types: + model = custom_type.get_model() + if model: + yield model + finally: + self._in_get_models = False config = CustomObjectsPluginConfig From 2b04436fd7866b7b02fa14754e93dd9e5c377767 Mon Sep 17 00:00:00 2001 From: Arthur Date: Wed, 13 Aug 2025 15:39:26 -0700 Subject: [PATCH 3/5] fix recursion --- netbox_custom_objects/__init__.py | 45 +++++++++++++++++++++++++------ 1 file changed, 37 insertions(+), 8 deletions(-) diff --git a/netbox_custom_objects/__init__.py b/netbox_custom_objects/__init__.py index bb7c08a..d93e40e 100644 --- a/netbox_custom_objects/__init__.py +++ b/netbox_custom_objects/__init__.py @@ -18,6 +18,33 @@ def is_running_migration(): return False +def is_in_clear_cache(): + """ + Check if the code is currently being called from Django's clear_cache() method. + + This is fairly ugly, but in models.CustomObjectType.get_model() we call + meta = type() which calls clear_cache on the model which causes a call to + get_models() which in-turn calls get_model and therefore recurses. + + This catches the specific case of a recursive call to get_models() from + clear_cache() which is the only case we care about, so should be relatively + safe. + """ + import inspect + frame = inspect.currentframe() + try: + # Walk up the call stack to see if we're being called from clear_cache + while frame: + if (frame.f_code.co_name == 'clear_cache' and + 'django/apps/registry.py' in frame.f_code.co_filename): + return True + frame = frame.f_back + return False + finally: + # Clean up the frame reference + del frame + + def check_custom_object_type_table_exists(): """ Check if the CustomObjectType table exists in the database. @@ -85,16 +112,18 @@ def get_model(self, model_name, require_ready=True): def get_models(self, include_auto_created=False, include_swapped=False): """Return all models for this plugin, including custom object type models.""" + + # Get the regular Django models first + for model in super().get_models(include_auto_created, include_swapped): + yield model + # Prevent recursion - if self._in_get_models: + if self._in_get_models and is_in_clear_cache(): + # Skip dynamic model creation if we're in a recursive get_models call return - + self._in_get_models = True try: - # Get the regular Django models first - for model in super().get_models(include_auto_created, include_swapped): - yield model - # Suppress warnings about database calls during model loading with warnings.catch_warnings(): warnings.filterwarnings( @@ -117,7 +146,7 @@ def get_models(self, include_auto_created=False, include_swapped=False): if model: yield model finally: + # Clean up the recursion guard self._in_get_models = False - - + config = CustomObjectsPluginConfig From bdbd76aa986d6ec197d9891c9376c5911b632ad1 Mon Sep 17 00:00:00 2001 From: Arthur Date: Wed, 13 Aug 2025 15:39:52 -0700 Subject: [PATCH 4/5] fix recursion --- netbox_custom_objects/__init__.py | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/netbox_custom_objects/__init__.py b/netbox_custom_objects/__init__.py index d93e40e..7926015 100644 --- a/netbox_custom_objects/__init__.py +++ b/netbox_custom_objects/__init__.py @@ -23,7 +23,7 @@ def is_in_clear_cache(): Check if the code is currently being called from Django's clear_cache() method. This is fairly ugly, but in models.CustomObjectType.get_model() we call - meta = type() which calls clear_cache on the model which causes a call to + meta = type() which calls clear_cache on the model which causes a call to get_models() which in-turn calls get_model and therefore recurses. This catches the specific case of a recursive call to get_models() from @@ -31,12 +31,15 @@ def is_in_clear_cache(): safe. """ import inspect + frame = inspect.currentframe() try: # Walk up the call stack to see if we're being called from clear_cache while frame: - if (frame.f_code.co_name == 'clear_cache' and - 'django/apps/registry.py' in frame.f_code.co_filename): + if ( + frame.f_code.co_name == "clear_cache" + and "django/apps/registry.py" in frame.f_code.co_filename + ): return True frame = frame.f_back return False @@ -112,7 +115,7 @@ def get_model(self, model_name, require_ready=True): def get_models(self, include_auto_created=False, include_swapped=False): """Return all models for this plugin, including custom object type models.""" - + # Get the regular Django models first for model in super().get_models(include_auto_created, include_swapped): yield model @@ -134,7 +137,10 @@ def get_models(self, include_auto_created=False, include_swapped=False): ) # Skip custom object type model loading if running during migration - if is_running_migration() or not check_custom_object_type_table_exists(): + if ( + is_running_migration() + or not check_custom_object_type_table_exists() + ): return # Add custom object type models @@ -148,5 +154,6 @@ def get_models(self, include_auto_created=False, include_swapped=False): finally: # Clean up the recursion guard self._in_get_models = False - + + config = CustomObjectsPluginConfig From 8b9ffa29379566b756100eacf460813bff1d09ff Mon Sep 17 00:00:00 2001 From: Arthur Date: Thu, 14 Aug 2025 12:58:53 -0700 Subject: [PATCH 5/5] add todo to comment --- netbox_custom_objects/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/netbox_custom_objects/__init__.py b/netbox_custom_objects/__init__.py index 7926015..764f551 100644 --- a/netbox_custom_objects/__init__.py +++ b/netbox_custom_objects/__init__.py @@ -22,13 +22,13 @@ def is_in_clear_cache(): """ Check if the code is currently being called from Django's clear_cache() method. - This is fairly ugly, but in models.CustomObjectType.get_model() we call + TODO: This is fairly ugly, but in models.CustomObjectType.get_model() we call meta = type() which calls clear_cache on the model which causes a call to get_models() which in-turn calls get_model and therefore recurses. This catches the specific case of a recursive call to get_models() from clear_cache() which is the only case we care about, so should be relatively - safe. + safe. An alternative should be found for this. """ import inspect