33import warnings
44
55from django .db import connection , transaction
6+ from django .db .migrations .loader import MigrationLoader
67from django .db .migrations .recorder import MigrationRecorder
78from django .db .models .signals import pre_migrate , post_migrate
8- from django .db .utils import DatabaseError , OperationalError , ProgrammingError
99from netbox .plugins import PluginConfig
1010
1111from .constants import APP_LABEL as APP_LABEL
1212
1313# Context variable to track if we're currently running migrations
1414_is_migrating = contextvars .ContextVar ('is_migrating' , default = False )
1515
16- # Minimum migration required for the plugin to function properly
17- # Update this when adding migrations that add fields to the plugin's models
18- REQUIRED_MIGRATION = '0003_ensure_fk_constraints'
16+ # Cache for migration check to avoid repeated expensive filesystem/database operations
17+ _migrations_checked = None
18+ _checking_migrations = False
1919
2020
2121def _migration_started (sender , ** kwargs ):
@@ -24,8 +24,10 @@ def _migration_started(sender, **kwargs):
2424
2525
2626def _migration_finished (sender , ** kwargs ):
27- """Signal handler for post_migrate - clears the migration flag."""
27+ """Signal handler for post_migrate - clears the migration flag and cache."""
28+ global _migrations_checked
2829 _is_migrating .set (False )
30+ _migrations_checked = None
2931
3032
3133# Plugin Configuration
@@ -54,10 +56,12 @@ def _should_skip_dynamic_model_creation():
5456 Returns True if dynamic models should not be created/loaded due to:
5557 - Currently running migrations
5658 - Running tests
57- - Required migration not yet applied
59+ - All migrations not yet applied
5860
5961 Returns False if it's safe to proceed with dynamic model creation.
6062 """
63+ global _migrations_checked , _checking_migrations
64+
6165 # Skip if currently running migrations
6266 if _is_migrating .get ():
6367 return True
@@ -66,17 +70,47 @@ def _should_skip_dynamic_model_creation():
6670 if "test" in sys .argv :
6771 return True
6872
69- # Skip if required migration hasn't been applied yet
70- try :
71- recorder = MigrationRecorder (connection )
72- applied_migrations = recorder .applied_migrations ()
73- if ('netbox_custom_objects' , REQUIRED_MIGRATION ) not in applied_migrations :
74- return True
75- except (DatabaseError , OperationalError , ProgrammingError ):
76- # If we can't check, assume migrations haven't been run
73+ # Below code is to check if the last migration is applied using the migration graph
74+ # However, migrations can can call into get_models() which can call into this function again
75+ # so we have checks to prevent recursion
76+ if _checking_migrations :
7777 return True
7878
79- return False
79+ # Return cached result if available
80+ if _migrations_checked is not None :
81+ return _migrations_checked
82+
83+ _checking_migrations = True
84+
85+ try :
86+ loader = MigrationLoader (connection )
87+
88+ # Get all migrations for our app from the migration graph
89+ app_migrations = [
90+ key [1 ] for key in loader .graph .nodes
91+ if key [0 ] == APP_LABEL
92+ ]
93+
94+ if not app_migrations :
95+ result = True
96+ else :
97+ # Get and check if the last migration is applied
98+ last_migration = sorted (app_migrations )[- 1 ]
99+ recorder = MigrationRecorder (connection )
100+ applied_migrations = recorder .applied_migrations ()
101+
102+ if (APP_LABEL , last_migration ) not in applied_migrations :
103+ result = True
104+ else :
105+ result = False
106+
107+ # Cache the result
108+ _migrations_checked = result
109+ return result
110+
111+ finally :
112+ # Always clear the recursion flag
113+ _checking_migrations = False
80114
81115 def ready (self ):
82116 from .models import CustomObjectType
0 commit comments