Skip to content

Commit 9b33510

Browse files
authored
Merge pull request #229 from netboxlabs/get-object-refactor-2
get_model / field_types refactor
2 parents af857f5 + 54598d3 commit 9b33510

File tree

4 files changed

+82
-214
lines changed

4 files changed

+82
-214
lines changed

netbox_custom_objects/__init__.py

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import sys
22
import warnings
33

4-
from django.core.exceptions import AppRegistryNotReady
54
from django.db import transaction
65
from django.db.utils import DatabaseError, OperationalError, ProgrammingError
76
from netbox.plugins import PluginConfig
@@ -52,16 +51,40 @@ class CustomObjectsPluginConfig(PluginConfig):
5251
required_settings = []
5352
template_extensions = "template_content.template_extensions"
5453

54+
def ready(self):
55+
from .models import CustomObjectType
56+
from netbox_custom_objects.api.serializers import get_serializer_class
57+
58+
# Suppress warnings about database calls during app initialization
59+
with warnings.catch_warnings():
60+
warnings.filterwarnings(
61+
"ignore", category=RuntimeWarning, message=".*database.*"
62+
)
63+
warnings.filterwarnings(
64+
"ignore", category=UserWarning, message=".*database.*"
65+
)
66+
67+
# Skip database calls if running during migration or if table doesn't exist
68+
if is_running_migration() or not check_custom_object_type_table_exists():
69+
super().ready()
70+
return
71+
72+
qs = CustomObjectType.objects.all()
73+
for obj in qs:
74+
model = obj.get_model()
75+
get_serializer_class(model)
76+
77+
super().ready()
78+
5579
def get_model(self, model_name, require_ready=True):
80+
self.apps.check_apps_ready()
5681
try:
5782
# if the model is already loaded, return it
5883
return super().get_model(model_name, require_ready)
5984
except LookupError:
60-
try:
61-
self.apps.check_apps_ready()
62-
except AppRegistryNotReady:
63-
raise
85+
pass
6486

87+
model_name = model_name.lower()
6588
# only do database calls if we are sure the app is ready to avoid
6689
# Django warnings
6790
if "table" not in model_name.lower() or "model" not in model_name.lower():

netbox_custom_objects/field_types.py

Lines changed: 5 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -396,7 +396,6 @@ def get_model_field(self, field, **kwargs):
396396
to_model = content_type.model
397397

398398
# Extract our custom parameters and keep only Django field parameters
399-
generating_models = kwargs.pop('_generating_models', getattr(self, '_generating_models', set()))
400399
field_kwargs = {k: v for k, v in kwargs.items() if not k.startswith('_')}
401400
field_kwargs.update({"default": field.default, "unique": field.unique})
402401

@@ -427,27 +426,7 @@ def get_model_field(self, field, **kwargs):
427426
return f
428427
else:
429428
# For cross-referential fields, use skip_object_fields to avoid infinite loops
430-
# Check if we're in a recursion situation using the parameter or stored attribute
431-
if generating_models and custom_object_type.id in generating_models:
432-
# We're in a circular reference, don't call get_model() to prevent recursion
433-
# Use a string reference instead
434-
model_name = f"{APP_LABEL}.{custom_object_type.get_table_model_name(custom_object_type.id)}"
435-
# Generate a unique related_name to prevent reverse accessor conflicts
436-
table_model_name = field.custom_object_type.get_table_model_name(
437-
field.custom_object_type.id
438-
).lower()
439-
related_name = f"{table_model_name}_{field.name}_set"
440-
f = models.ForeignKey(
441-
model_name,
442-
null=True,
443-
blank=True,
444-
on_delete=models.CASCADE,
445-
related_name=related_name,
446-
**field_kwargs
447-
)
448-
return f
449-
else:
450-
model = custom_object_type.get_model(skip_object_fields=True)
429+
model = custom_object_type.get_model(skip_object_fields=True)
451430
else:
452431
# to_model = content_type.model_class()._meta.object_name
453432
to_ct = f"{content_type.app_label}.{to_model}"
@@ -479,19 +458,7 @@ def get_form_field(self, field, for_csv_import=False, **kwargs):
479458
)
480459
custom_object_type = CustomObjectType.objects.get(pk=custom_object_type_id)
481460

482-
# Check if we're in a recursion situation
483-
generating_models = getattr(self, '_generating_models', set())
484-
if generating_models and custom_object_type.id in generating_models:
485-
# We're in a circular reference, don't call get_model() to prevent recursion
486-
# Use a minimal approach or return a basic field
487-
return DynamicModelChoiceField(
488-
queryset=custom_object_type.get_model(skip_object_fields=True).objects.all(),
489-
required=field.required,
490-
# Remove initial=field.default to allow Django to handle instance data properly
491-
selector=True,
492-
)
493-
else:
494-
model = custom_object_type.get_model()
461+
model = custom_object_type.get_model()
495462
else:
496463
# This is a regular NetBox model
497464
model = content_type.model_class()
@@ -801,20 +768,7 @@ def get_form_field(self, field, for_csv_import=False, **kwargs):
801768
)
802769
custom_object_type = CustomObjectType.objects.get(pk=custom_object_type_id)
803770

804-
# For cross-referential fields, use skip_object_fields to avoid infinite loops
805-
# Check if we're in a recursion situation using the parameter or stored attribute
806-
generating_models = getattr(self, '_generating_models', set())
807-
if generating_models and custom_object_type.id in generating_models:
808-
# We're in a circular reference, don't call get_model() to prevent recursion
809-
# Use a minimal approach or return a basic field
810-
return DynamicModelMultipleChoiceField(
811-
queryset=custom_object_type.get_model(skip_object_fields=True).objects.all(),
812-
required=field.required,
813-
# Remove initial=field.default to allow Django to handle instance data properly
814-
selector=True,
815-
)
816-
else:
817-
model = custom_object_type.get_model(skip_object_fields=True)
771+
model = custom_object_type.get_model(skip_object_fields=True)
818772
else:
819773
# This is a regular NetBox model
820774
model = content_type.model_class()
@@ -905,13 +859,7 @@ def after_model_generation(self, instance, model, field_name):
905859
# Self-referential field - resolve to current model
906860
to_model = model
907861
else:
908-
# Cross-referential field - check for recursion before calling get_model()
909-
generating_models = getattr(self, '_generating_models', set())
910-
if generating_models and custom_object_type.id in generating_models:
911-
# We're in a circular reference, don't call get_model() to prevent recursion
912-
return
913-
else:
914-
to_model = custom_object_type.get_model()
862+
to_model = custom_object_type.get_model()
915863
else:
916864
to_ct = f"{content_type.app_label}.{content_type.model}"
917865
to_model = apps.get_model(to_ct)
@@ -956,14 +904,7 @@ def create_m2m_table(self, instance, model, field_name):
956904
pk=custom_object_type_id
957905
)
958906

959-
# Check if we're in a recursion situation
960-
generating_models = getattr(self, '_generating_models', set())
961-
if generating_models and custom_object_type.id in generating_models:
962-
# We're in a circular reference, don't call get_model() to prevent recursion
963-
# Use a minimal approach or skip this field
964-
return
965-
else:
966-
to_model = custom_object_type.get_model()
907+
to_model = custom_object_type.get_model()
967908
else:
968909
to_model = content_type.model_class()
969910

0 commit comments

Comments
 (0)