Skip to content

Commit ef54f1c

Browse files
committed
remove generating models
1 parent 17466f3 commit ef54f1c

File tree

2 files changed

+26
-114
lines changed

2 files changed

+26
-114
lines changed

netbox_custom_objects/field_types.py

Lines changed: 23 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -408,20 +408,25 @@ def get_model_field(self, field, **kwargs):
408408
)
409409
custom_object_type = CustomObjectType.objects.get(pk=custom_object_type_id)
410410

411-
# For self-referential fields, use LazyForeignKey to defer resolution
412-
model_name = f"{APP_LABEL}.{custom_object_type.get_table_model_name(custom_object_type.id)}"
413-
# Generate a unique related_name to prevent reverse accessor conflicts
414-
table_model_name = field.custom_object_type.get_table_model_name(field.custom_object_type.id).lower()
415-
related_name = f"{table_model_name}_{field.name}_set"
416-
f = LazyForeignKey(
417-
model_name,
418-
null=True,
419-
blank=True,
420-
on_delete=models.CASCADE,
421-
related_name=related_name,
422-
**field_kwargs
423-
)
424-
return f
411+
# Check if this is a self-referential field
412+
if custom_object_type.id == field.custom_object_type.id:
413+
# For self-referential fields, use LazyForeignKey to defer resolution
414+
model_name = f"{APP_LABEL}.{custom_object_type.get_table_model_name(custom_object_type.id)}"
415+
# Generate a unique related_name to prevent reverse accessor conflicts
416+
table_model_name = field.custom_object_type.get_table_model_name(field.custom_object_type.id).lower()
417+
related_name = f"{table_model_name}_{field.name}_set"
418+
f = LazyForeignKey(
419+
model_name,
420+
null=True,
421+
blank=True,
422+
on_delete=models.CASCADE,
423+
related_name=related_name,
424+
**field_kwargs
425+
)
426+
return f
427+
else:
428+
# For cross-referential fields, use skip_object_fields to avoid infinite loops
429+
model = custom_object_type.get_model(skip_object_fields=True)
425430
else:
426431
# to_model = content_type.model_class()._meta.object_name
427432
to_ct = f"{content_type.app_label}.{to_model}"
@@ -453,19 +458,7 @@ def get_form_field(self, field, for_csv_import=False, **kwargs):
453458
)
454459
custom_object_type = CustomObjectType.objects.get(pk=custom_object_type_id)
455460

456-
# Check if we're in a recursion situation
457-
generating_models = getattr(self, '_generating_models', set())
458-
if generating_models and custom_object_type.id in generating_models:
459-
# We're in a circular reference, don't call get_model() to prevent recursion
460-
# Use a minimal approach or return a basic field
461-
return DynamicModelChoiceField(
462-
queryset=custom_object_type.get_model(skip_object_fields=True).objects.all(),
463-
required=field.required,
464-
# Remove initial=field.default to allow Django to handle instance data properly
465-
selector=True,
466-
)
467-
else:
468-
model = custom_object_type.get_model()
461+
model = custom_object_type.get_model()
469462
else:
470463
# This is a regular NetBox model
471464
model = content_type.model_class()
@@ -775,20 +768,7 @@ def get_form_field(self, field, for_csv_import=False, **kwargs):
775768
)
776769
custom_object_type = CustomObjectType.objects.get(pk=custom_object_type_id)
777770

778-
# For cross-referential fields, use skip_object_fields to avoid infinite loops
779-
# Check if we're in a recursion situation using the parameter or stored attribute
780-
generating_models = getattr(self, '_generating_models', set())
781-
if generating_models and custom_object_type.id in generating_models:
782-
# We're in a circular reference, don't call get_model() to prevent recursion
783-
# Use a minimal approach or return a basic field
784-
return DynamicModelMultipleChoiceField(
785-
queryset=custom_object_type.get_model(skip_object_fields=True).objects.all(),
786-
required=field.required,
787-
# Remove initial=field.default to allow Django to handle instance data properly
788-
selector=True,
789-
)
790-
else:
791-
model = custom_object_type.get_model(skip_object_fields=True)
771+
model = custom_object_type.get_model(skip_object_fields=True)
792772
else:
793773
# This is a regular NetBox model
794774
model = content_type.model_class()
@@ -879,13 +859,7 @@ def after_model_generation(self, instance, model, field_name):
879859
# Self-referential field - resolve to current model
880860
to_model = model
881861
else:
882-
# Cross-referential field - check for recursion before calling get_model()
883-
generating_models = getattr(self, '_generating_models', set())
884-
if generating_models and custom_object_type.id in generating_models:
885-
# We're in a circular reference, don't call get_model() to prevent recursion
886-
return
887-
else:
888-
to_model = custom_object_type.get_model()
862+
to_model = custom_object_type.get_model()
889863
else:
890864
to_ct = f"{content_type.app_label}.{content_type.model}"
891865
to_model = apps.get_model(to_ct)
@@ -930,14 +904,7 @@ def create_m2m_table(self, instance, model, field_name):
930904
pk=custom_object_type_id
931905
)
932906

933-
# Check if we're in a recursion situation
934-
generating_models = getattr(self, '_generating_models', set())
935-
if generating_models and custom_object_type.id in generating_models:
936-
# We're in a circular reference, don't call get_model() to prevent recursion
937-
# Use a minimal approach or skip this field
938-
return
939-
else:
940-
to_model = custom_object_type.get_model()
907+
to_model = custom_object_type.get_model()
941908
else:
942909
to_model = content_type.model_class()
943910

netbox_custom_objects/models.py

Lines changed: 3 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -229,15 +229,6 @@ def clear_model_cache(cls, custom_object_type_id=None):
229229
# Clear Django apps registry cache to ensure newly created models are recognized
230230
apps.get_models.cache_clear()
231231

232-
# Clear global recursion tracking when clearing cache
233-
cls.clear_global_recursion_tracking()
234-
235-
@classmethod
236-
def clear_global_recursion_tracking(cls):
237-
"""Clear the global recursion tracking set."""
238-
if hasattr(cls, '_global_generating_models'):
239-
cls._global_generating_models.clear()
240-
241232
@classmethod
242233
def get_cached_model(cls, custom_object_type_id):
243234
"""
@@ -314,7 +305,6 @@ def _fetch_and_generate_field_attrs(
314305
self,
315306
fields,
316307
skip_object_fields=False,
317-
generating_models=None,
318308
):
319309
field_attrs = {
320310
"_primary_field_id": -1,
@@ -338,17 +328,10 @@ def _fetch_and_generate_field_attrs(
338328
field_type = FIELD_TYPE_CLASS[field.type]()
339329
field_name = field.name
340330

341-
# Pass generating models set to field generation to prevent infinite loops
342-
field_type._generating_models = generating_models
343-
344331
# Check if we're in a recursion situation before generating the field
345332
# Use depth-based recursion control: allow self-referential fields at level 0, skip at deeper levels
346333
should_skip = False
347334

348-
# Calculate depth correctly: depth 0 is when we're generating the main model
349-
# depth 1+ is when we're generating related models recursively
350-
current_depth = len(generating_models) - 1 if generating_models else 0
351-
352335
if field.type in [CustomFieldTypeChoices.TYPE_OBJECT, CustomFieldTypeChoices.TYPE_MULTIOBJECT]:
353336
if field.related_object_type:
354337
# Check if this field references the same CustomObjectType (self-referential)
@@ -366,23 +349,8 @@ def _fetch_and_generate_field_attrs(
366349
if id_match:
367350
custom_object_type_id = int(id_match.group(1))
368351

369-
if custom_object_type_id == self.id:
370-
# This is a self-referential field
371-
if current_depth == 0:
372-
# At level 0, allow self-referential fields
373-
should_skip = False
374-
else:
375-
# At deeper levels, skip self-referential fields to prevent infinite recursion
376-
should_skip = True
377-
378-
if should_skip:
379-
# Skip this field to prevent further recursion
380-
field_attrs["_skipped_fields"].add(field.name)
381-
continue
382-
383352
field_attrs[field.name] = field_type.get_model_field(
384353
field,
385-
_generating_models=generating_models, # Pass as prefixed parameter
386354
)
387355

388356
# Add to field objects only if the field was successfully generated
@@ -493,7 +461,6 @@ def register_custom_object_search_index(self, model):
493461
def get_model(
494462
self,
495463
skip_object_fields=False,
496-
_generating_models=None,
497464
):
498465
"""
499466
Generates a temporary Django model based on available fields that belong to
@@ -503,8 +470,6 @@ def get_model(
503470
:type fields: list
504471
:param skip_object_fields: Don't add object or multiobject fields to the model
505472
:type skip_object_fields: bool
506-
:param _generating_models: Internal parameter to track models being generated
507-
:type _generating_models: set
508473
:return: The generated model.
509474
:rtype: Model
510475
"""
@@ -514,16 +479,6 @@ def get_model(
514479
model = self.get_cached_model(self.id)
515480
return model
516481

517-
# Circular reference detection using class-level tracking
518-
if not hasattr(CustomObjectType, '_global_generating_models'):
519-
CustomObjectType._global_generating_models = set()
520-
521-
if _generating_models is None:
522-
_generating_models = CustomObjectType._global_generating_models
523-
524-
# Add this model to the set of models being generated
525-
_generating_models.add(self.id)
526-
527482
model_name = self.get_table_model_name(self.pk)
528483

529484
# TODO: Add other fields with "index" specified
@@ -556,7 +511,6 @@ def get_model(
556511
field_attrs = self._fetch_and_generate_field_attrs(
557512
fields,
558513
skip_object_fields=skip_object_fields,
559-
generating_models=_generating_models
560514
)
561515

562516
attrs.update(**field_attrs)
@@ -589,9 +543,9 @@ def wrapped_post_through_setup(self, cls):
589543
TM.post_through_setup = original_post_through_setup
590544

591545
# Register the main model with Django's app registry
592-
if model_name in apps.all_models[APP_LABEL]:
546+
if model_name.lower() in apps.all_models[APP_LABEL]:
593547
# Remove the existing model from all_models before registering the new one
594-
del apps.all_models[APP_LABEL][model_name]
548+
del apps.all_models[APP_LABEL][model_name.lower()]
595549

596550
apps.register_model(APP_LABEL, model)
597551
'''
@@ -613,6 +567,7 @@ def wrapped_post_through_setup(self, cls):
613567
# Do the clear cache now that we have it in the cache so there
614568
# is no recursion.
615569
apps.clear_cache()
570+
ContentType.objects.clear_cache()
616571

617572
'''
618573
# Register the serializer for this model
@@ -624,16 +579,6 @@ def wrapped_post_through_setup(self, cls):
624579
# Register the global SearchIndex for this model
625580
self.register_custom_object_search_index(model)
626581

627-
# Clean up: remove this model from the set of models being generated
628-
if _generating_models is not None:
629-
_generating_models.discard(self.id)
630-
# Also clean up from global tracking if this is the global set
631-
if _generating_models is CustomObjectType._global_generating_models:
632-
CustomObjectType._global_generating_models.discard(self.id)
633-
# Clear global tracking when we're done to ensure clean state
634-
if len(CustomObjectType._global_generating_models) == 0:
635-
CustomObjectType._global_generating_models.clear()
636-
637582
return model
638583

639584
def create_model(self):

0 commit comments

Comments
 (0)