11import sys
22import warnings
33
4+ from django .db import transaction
45from django .db .utils import DatabaseError , OperationalError , ProgrammingError
56from netbox .plugins import PluginConfig
67
78from .constants import APP_LABEL as APP_LABEL
89
910
10- def is_running_migration ():
11- """
12- Check if the code is currently running during a Django migration.
13- """
14- # Check if 'makemigrations' or 'migrate' command is in sys.argv
15- if any (cmd in sys .argv for cmd in ["makemigrations" , "migrate" ]):
16- return True
17-
18- return False
19-
20-
21- def check_custom_object_type_table_exists ():
22- """
23- Check if the CustomObjectType table exists in the database.
24- Returns True if the table exists, False otherwise.
25- """
26- from django .db import connection
27- from .models import CustomObjectType
28-
29- try :
30- # Use raw SQL to check table existence without generating ORM errors
31- with connection .cursor () as cursor :
32- table_name = CustomObjectType ._meta .db_table
33- cursor .execute ("""
34- SELECT EXISTS (
35- SELECT FROM information_schema.tables
36- WHERE table_name = %s
37- )
38- """ , [table_name ])
39- table_exists = cursor .fetchone ()[0 ]
40- return table_exists
41- except (OperationalError , ProgrammingError , DatabaseError ):
42- # Catch database-specific errors (permission issues, etc.)
43- return False
44-
45-
4611# Plugin Configuration
4712class CustomObjectsPluginConfig (PluginConfig ):
4813 name = "netbox_custom_objects"
@@ -60,6 +25,47 @@ class CustomObjectsPluginConfig(PluginConfig):
6025 required_settings = []
6126 template_extensions = "template_content.template_extensions"
6227
28+ @staticmethod
29+ def _is_running_migration ():
30+ """
31+ Check if the code is currently running during a Django migration.
32+ """
33+ # Check if 'makemigrations' or 'migrate' command is in sys.argv
34+ return any (cmd in sys .argv for cmd in ["makemigrations" , "migrate" ])
35+
36+ @staticmethod
37+ def _is_running_test ():
38+ """
39+ Check if the code is currently running during Django tests.
40+ """
41+ # Check if 'test' command is in sys.argv
42+ return "test" in sys .argv
43+
44+ @staticmethod
45+ def _check_custom_object_type_table_exists ():
46+ """
47+ Check if the CustomObjectType table exists in the database.
48+ Returns True if the table exists, False otherwise.
49+ """
50+ from django .db import connection
51+ from .models import CustomObjectType
52+
53+ try :
54+ # Use raw SQL to check table existence without generating ORM errors
55+ with connection .cursor () as cursor :
56+ table_name = CustomObjectType ._meta .db_table
57+ cursor .execute ("""
58+ SELECT EXISTS (
59+ SELECT FROM information_schema.tables
60+ WHERE table_name = %s
61+ )
62+ """ , [table_name ])
63+ table_exists = cursor .fetchone ()[0 ]
64+ return table_exists
65+ except (OperationalError , ProgrammingError , DatabaseError ):
66+ # Catch database-specific errors (permission issues, etc.)
67+ return False
68+
6369 def ready (self ):
6470 from .models import CustomObjectType
6571 from netbox_custom_objects .api .serializers import get_serializer_class
@@ -74,14 +80,24 @@ def ready(self):
7480 )
7581
7682 # Skip database calls if running during migration or if table doesn't exist
77- if is_running_migration () or not check_custom_object_type_table_exists ():
83+ if self . _is_running_migration () or not self . _check_custom_object_type_table_exists ():
7884 super ().ready ()
7985 return
8086
81- qs = CustomObjectType .objects .all ()
82- for obj in qs :
83- model = obj .get_model ()
84- get_serializer_class (model )
87+ try :
88+ with transaction .atomic ():
89+ qs = CustomObjectType .objects .all ()
90+ for obj in qs :
91+ model = obj .get_model ()
92+ get_serializer_class (model )
93+ except (DatabaseError , OperationalError , ProgrammingError ):
94+ # Only suppress exceptions during tests when schema may not match model
95+ # During normal operation, re-raise to alert of actual problems
96+ if self ._is_running_test ():
97+ # The transaction.atomic() block will automatically rollback
98+ pass
99+ else :
100+ raise
85101
86102 super ().ready ()
87103
@@ -132,22 +148,33 @@ def get_models(self, include_auto_created=False, include_swapped=False):
132148 )
133149
134150 # Skip custom object type model loading if running during migration
135- if is_running_migration () or not check_custom_object_type_table_exists ():
151+ if self . _is_running_migration () or not self . _check_custom_object_type_table_exists ():
136152 return
137153
138154 # Add custom object type models
139155 from .models import CustomObjectType
140156
141- custom_object_types = CustomObjectType .objects .all ()
142- for custom_type in custom_object_types :
143- model = custom_type .get_model ()
144- if model :
145- yield model
146-
147- # If include_auto_created is True, also yield through models
148- if include_auto_created and hasattr (model , '_through_models' ):
149- for through_model in model ._through_models :
150- yield through_model
157+ try :
158+ with transaction .atomic ():
159+ custom_object_types = CustomObjectType .objects .all ()
160+ for custom_type in custom_object_types :
161+ model = custom_type .get_model ()
162+ if model :
163+ yield model
164+
165+ # If include_auto_created is True, also yield through models
166+ if include_auto_created and hasattr (model , '_through_models' ):
167+ for through_model in model ._through_models :
168+ yield through_model
169+ except (DatabaseError , OperationalError , ProgrammingError ):
170+ # Only suppress exceptions during tests when schema may not match model
171+ # (e.g., cache_timestamp column doesn't exist yet during test setup)
172+ # During normal operation, re-raise to alert of actual problems
173+ if self ._is_running_test ():
174+ # The transaction.atomic() block will automatically rollback
175+ pass
176+ else :
177+ raise
151178
152179
153180config = CustomObjectsPluginConfig
0 commit comments