Skip to content

Commit b8620d4

Browse files
committed
Remove unnecessary minimal_model code and use LazyForeignKey for both logic branches in single object fields
1 parent d0b6619 commit b8620d4

File tree

2 files changed

+23
-114
lines changed

2 files changed

+23
-114
lines changed

netbox_custom_objects/field_types.py

Lines changed: 23 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,19 @@
1414
from django.utils.translation import gettext_lazy as _
1515
from extras.choices import CustomFieldTypeChoices, CustomFieldUIEditableChoices
1616
from utilities.api import get_serializer_for_model
17-
from utilities.forms.fields import (CSVChoiceField, CSVModelChoiceField,
18-
CSVModelMultipleChoiceField, CSVMultipleChoiceField,
19-
DynamicChoiceField,
20-
DynamicMultipleChoiceField, JSONField,
21-
LaxURLField)
17+
from utilities.forms.fields import (
18+
CSVChoiceField, CSVModelChoiceField,
19+
CSVModelMultipleChoiceField, CSVMultipleChoiceField,
20+
DynamicChoiceField, DynamicModelChoiceField,
21+
DynamicModelMultipleChoiceField,
22+
DynamicMultipleChoiceField, JSONField,
23+
LaxURLField,
24+
)
2225
from utilities.forms.utils import add_blank_choice
23-
from utilities.forms.widgets import (APISelect, APISelectMultiple, DatePicker,
24-
DateTimePicker)
26+
from utilities.forms.widgets import (
27+
APISelect, APISelectMultiple, DatePicker,
28+
DateTimePicker,
29+
)
2530
from utilities.templatetags.builtins.filters import linkify, render_markdown
2631

2732
from netbox_custom_objects.constants import APP_LABEL
@@ -73,12 +78,6 @@ def get_filterform_field(self, field, **kwargs):
7378
def get_form_field(self, field, **kwargs):
7479
raise NotImplementedError
7580

76-
def _filter_field_kwargs(self, kwargs):
77-
"""
78-
Filter out custom parameters that shouldn't be passed to Django field constructors.
79-
"""
80-
return {k: v for k, v in kwargs.items() if not k.startswith('_') and k != 'generating_models'}
81-
8281
def _safe_kwargs(self, **kwargs):
8382
"""
8483
Create a safe kwargs dict that can be passed to Django field constructors.
@@ -389,8 +388,7 @@ def get_model_field(self, field, **kwargs):
389388
to_model = content_type.model
390389

391390
# Extract our custom parameters and keep only Django field parameters
392-
generating_models = kwargs.pop('_generating_models', getattr(self, '_generating_models', set()))
393-
field_kwargs = {k: v for k, v in kwargs.items() if not k.startswith('_')}
391+
field_kwargs = self._safe_kwargs(**kwargs)
394392
field_kwargs.update({"default": field.default, "unique": field.unique})
395393

396394
# Handle self-referential fields by using string references
@@ -402,35 +400,15 @@ def get_model_field(self, field, **kwargs):
402400
)
403401
custom_object_type = CustomObjectType.objects.get(pk=custom_object_type_id)
404402

405-
# Check if this is a self-referential field
406-
if custom_object_type.id == field.custom_object_type.id:
407-
# For self-referential fields, use LazyForeignKey to defer resolution
408-
model_name = f"{APP_LABEL}.{custom_object_type.get_table_model_name(custom_object_type.id)}"
409-
f = LazyForeignKey(
410-
model_name,
411-
null=True,
412-
blank=True,
413-
on_delete=models.CASCADE,
414-
**field_kwargs
415-
)
416-
return f
417-
else:
418-
# For cross-referential fields, use skip_object_fields to avoid infinite loops
419-
# Check if we're in a recursion situation using the parameter or stored attribute
420-
if generating_models and custom_object_type.id in generating_models:
421-
# We're in a circular reference, don't call get_model() to prevent recursion
422-
# Use a string reference instead
423-
model_name = f"{APP_LABEL}.{custom_object_type.get_table_model_name(custom_object_type.id)}"
424-
f = models.ForeignKey(
425-
model_name,
426-
null=True,
427-
blank=True,
428-
on_delete=models.CASCADE,
429-
**field_kwargs
430-
)
431-
return f
432-
else:
433-
model = custom_object_type.get_model(skip_object_fields=True)
403+
model_name = f"{APP_LABEL}.{custom_object_type.get_table_model_name(custom_object_type.id)}"
404+
f = LazyForeignKey(
405+
model_name,
406+
null=True,
407+
blank=True,
408+
on_delete=models.CASCADE,
409+
**field_kwargs
410+
)
411+
return f
434412
else:
435413
# to_model = content_type.model_class()._meta.object_name
436414
to_ct = f"{content_type.app_label}.{to_model}"
@@ -464,7 +442,6 @@ def get_form_field(self, field, for_csv_import=False, **kwargs):
464442
if generating_models and custom_object_type.id in generating_models:
465443
# We're in a circular reference, don't call get_model() to prevent recursion
466444
# Use a minimal approach or return a basic field
467-
from utilities.forms.fields import DynamicModelChoiceField
468445
return DynamicModelChoiceField(
469446
queryset=custom_object_type.get_model(skip_object_fields=True).objects.all(),
470447
required=field.required,
@@ -488,7 +465,6 @@ def get_form_field(self, field, for_csv_import=False, **kwargs):
488465
to_field_name=to_field_name,
489466
)
490467
else:
491-
from utilities.forms.fields import DynamicModelChoiceField
492468
field_class = DynamicModelChoiceField
493469
return field_class(
494470
queryset=model.objects.all(),
@@ -523,8 +499,7 @@ def after_model_generation(self, instance, model, field_name):
523499
This ensures that self-referential fields point to the correct model class.
524500
"""
525501
# Check if this field has a resolution method
526-
resolve_method = getattr(model, f'_resolve_{field_name}_model', None)
527-
if resolve_method:
502+
if resolve_method := getattr(model, f'_resolve_{field_name}_model', None):
528503
resolve_method(model)
529504

530505

@@ -790,7 +765,6 @@ def get_form_field(self, field, for_csv_import=False, **kwargs):
790765
if generating_models and custom_object_type.id in generating_models:
791766
# We're in a circular reference, don't call get_model() to prevent recursion
792767
# Use a minimal approach or return a basic field
793-
from utilities.forms.fields import DynamicModelMultipleChoiceField
794768
return DynamicModelMultipleChoiceField(
795769
queryset=custom_object_type.get_model(skip_object_fields=True).objects.all(),
796770
required=field.required,
@@ -814,7 +788,6 @@ def get_form_field(self, field, for_csv_import=False, **kwargs):
814788
to_field_name=to_field_name,
815789
)
816790
else:
817-
from utilities.forms.fields import DynamicModelMultipleChoiceField
818791
field_class = DynamicModelMultipleChoiceField
819792
return field_class(
820793
queryset=model.objects.all(),
@@ -839,8 +812,6 @@ def get_serializer_field(self, field, **kwargs):
839812
if related_model_class._meta.app_label == APP_LABEL:
840813
from netbox_custom_objects.api.serializers import get_serializer_class
841814
serializer = get_serializer_class(related_model_class, skip_object_fields=True)
842-
# if not related_model_class:
843-
# raise NotImplementedError("Custom object serializers not implemented")
844815
else:
845816
serializer = get_serializer_for_model(related_model_class)
846817
return serializer(required=field.required, nested=True, many=True)

netbox_custom_objects/models.py

Lines changed: 0 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -378,7 +378,6 @@ def _fetch_and_generate_field_attrs(
378378
model_name = content_type.model
379379

380380
# Try to extract the ID from the model name
381-
import re
382381
id_match = re.search(r'table(\d+)model', model_name, re.IGNORECASE)
383382
if id_match:
384383
custom_object_type_id = int(id_match.group(1))
@@ -539,15 +538,6 @@ def get_model(
539538

540539
if _generating_models is None:
541540
_generating_models = CustomObjectType._global_generating_models
542-
# Check if we're already generating this model (circular reference)
543-
if self.id in _generating_models:
544-
# We have a circular reference, return a minimal model to break the cycle
545-
return self._get_minimal_model(_generating_models)
546-
547-
# Check recursion depth to prevent infinite loops
548-
if len(_generating_models) > 1:
549-
# We're too deep in recursion, return a minimal model
550-
return self._get_minimal_model(_generating_models)
551541

552542
# Add this model to the set of models being generated
553543
_generating_models.add(self.id)
@@ -667,58 +657,6 @@ def wrapped_post_through_setup(self, cls):
667657

668658
return model
669659

670-
def _get_minimal_model(self, _generating_models=None):
671-
"""
672-
Creates a minimal model with only basic fields when recursion is detected.
673-
This breaks infinite recursion cycles by providing a basic model structure.
674-
"""
675-
model_name = self.get_table_model_name(self.pk)
676-
677-
meta = type(
678-
"Meta",
679-
(),
680-
{
681-
"apps": apps,
682-
"managed": False,
683-
"db_table": self.get_database_table_name(),
684-
"app_label": APP_LABEL,
685-
"ordering": ["id"],
686-
"verbose_name": self.get_verbose_name(),
687-
"verbose_name_plural": self.get_verbose_name_plural(),
688-
},
689-
)
690-
691-
attrs = {
692-
"Meta": meta,
693-
"__module__": "database.models",
694-
"custom_object_type": self,
695-
"custom_object_type_id": self.id,
696-
}
697-
698-
# Don't call field generation methods to avoid further recursion
699-
# Just create a basic model with no custom fields
700-
# attrs.update(**field_attrs)
701-
702-
# Create the minimal model
703-
model = type(
704-
str(model_name),
705-
(CustomObject, models.Model),
706-
attrs,
707-
)
708-
709-
# Register the model with Django's app registry
710-
try:
711-
existing_model = apps.get_model(APP_LABEL, model_name)
712-
if existing_model is not model:
713-
model = existing_model
714-
except LookupError:
715-
apps.register_model(APP_LABEL, model)
716-
717-
# Cache this minimal model
718-
self._model_cache[self.id] = model
719-
720-
return model
721-
722660
def create_model(self):
723661
# Get the model and ensure it's registered
724662
model = self.get_model()

0 commit comments

Comments
 (0)