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
89from django .db .utils import DatabaseError , OperationalError , ProgrammingError
1314# Context variable to track if we're currently running migrations
1415_is_migrating = contextvars .ContextVar ('is_migrating' , default = False )
1516
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'
17+ # Cache for migration check to avoid repeated expensive filesystem/database operations
18+ _migrations_checked = None
19+ _checking_migrations = False
1920
2021
2122def _migration_started (sender , ** kwargs ):
@@ -24,8 +25,11 @@ def _migration_started(sender, **kwargs):
2425
2526
2627def _migration_finished (sender , ** kwargs ):
27- """Signal handler for post_migrate - clears the migration flag."""
28+ """Signal handler for post_migrate - clears the migration flag and cache."""
29+ global _migrations_checked
2830 _is_migrating .set (False )
31+ # Clear the cache after migrations run so we check again
32+ _migrations_checked = None
2933
3034
3135# Plugin Configuration
@@ -54,10 +58,12 @@ def _should_skip_dynamic_model_creation():
5458 Returns True if dynamic models should not be created/loaded due to:
5559 - Currently running migrations
5660 - Running tests
57- - Required migration not yet applied
61+ - All migrations not yet applied
5862
5963 Returns False if it's safe to proceed with dynamic model creation.
6064 """
65+ global _migrations_checked , _checking_migrations
66+
6167 # Skip if currently running migrations
6268 if _is_migrating .get ():
6369 return True
@@ -66,17 +72,47 @@ def _should_skip_dynamic_model_creation():
6672 if "test" in sys .argv :
6773 return True
6874
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
75+ # Below code is to check if the last migration is applied using the migration graph
76+ # However, migrations can can call into get_models() which can call into this function again
77+ # so we have checks to prevent recursion
78+ if _checking_migrations :
7779 return True
7880
79- return False
81+ # Return cached result if available
82+ if _migrations_checked is not None :
83+ return _migrations_checked
84+
85+ _checking_migrations = True
86+
87+ try :
88+ loader = MigrationLoader (connection )
89+
90+ # Get all migrations for our app from the migration graph
91+ app_migrations = [
92+ key [1 ] for key in loader .graph .nodes
93+ if key [0 ] == APP_LABEL
94+ ]
95+
96+ if not app_migrations :
97+ result = True
98+ else :
99+ # Get and check if the last migration is applied
100+ last_migration = sorted (app_migrations )[- 1 ]
101+ recorder = MigrationRecorder (connection )
102+ applied_migrations = recorder .applied_migrations ()
103+
104+ if (APP_LABEL , last_migration ) not in applied_migrations :
105+ result = True
106+ else :
107+ result = False
108+
109+ # Cache the result
110+ _migrations_checked = result
111+ return result
112+
113+ finally :
114+ # Always clear the recursion flag
115+ _checking_migrations = False
80116
81117 def ready (self ):
82118 from .models import CustomObjectType
0 commit comments