Skip to content

Commit a01f819

Browse files
committed
fix save of content type
1 parent 08ee63b commit a01f819

File tree

2 files changed

+95
-13
lines changed

2 files changed

+95
-13
lines changed

netbox_custom_objects/models.py

Lines changed: 94 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@
1414
from django.db import connection, IntegrityError, models, transaction
1515
from django.db.models import Q
1616
from django.db.models.functions import Lower
17-
from django.db.models.signals import pre_delete
17+
from django.db.models.signals import pre_delete, post_save
18+
from django.dispatch import receiver
1819
from django.urls import reverse
1920
from django.utils.translation import gettext_lazy as _
2021
from core.signals import handle_deleted_object
@@ -671,17 +672,8 @@ def save(self, *args, **kwargs):
671672
needs_db_create = self._state.adding
672673

673674
super().save(*args, **kwargs)
674-
if needs_db_create:
675-
# If creating a new object, get or create the ObjectType
676-
content_type_name = self.get_table_model_name(self.id).lower()
677-
ct, created = ObjectType.objects.get_or_create(
678-
app_label=APP_LABEL,
679-
model=content_type_name
680-
)
681-
# Force a refresh to ensure it's available in the current transaction
682-
# ct.refresh_from_db()
683-
self.object_type = ct
684675

676+
if needs_db_create:
685677
self.create_model()
686678
else:
687679
# Clear the model cache when the CustomObjectType is modified
@@ -694,7 +686,7 @@ def delete(self, *args, **kwargs):
694686
model = self.get_model()
695687

696688
# Delete all CustomObjectTypeFields that reference this CustomObjectType
697-
for field in CustomObjectTypeField.objects.filter(related_object_type=self.content_type):
689+
for field in CustomObjectTypeField.objects.filter(related_object_type=self.object_type):
698690
field.delete()
699691

700692
object_type = ObjectType.objects.get_for_model(model)
@@ -710,6 +702,19 @@ def delete(self, *args, **kwargs):
710702
pre_delete.connect(handle_deleted_object)
711703

712704

705+
@receiver(post_save, sender=CustomObjectType)
706+
def custom_object_type_post_save_handler(sender, instance, created, **kwargs):
707+
if created:
708+
# If creating a new object, get or create the ObjectType
709+
content_type_name = instance.get_table_model_name(instance.id).lower()
710+
ct, created = ObjectType.objects.get_or_create(
711+
app_label=APP_LABEL,
712+
model=content_type_name
713+
)
714+
instance.object_type = ct
715+
instance.save()
716+
717+
713718
class CustomObjectTypeField(CloningMixin, ExportTemplatesMixin, ChangeLoggedModel):
714719
custom_object_type = models.ForeignKey(
715720
CustomObjectType, on_delete=models.CASCADE, related_name="fields"
@@ -1118,6 +1123,83 @@ def clean(self):
11181123
}
11191124
)
11201125

1126+
# Check for recursion in object and multiobject fields
1127+
if (self.type in (
1128+
CustomFieldTypeChoices.TYPE_OBJECT,
1129+
CustomFieldTypeChoices.TYPE_MULTIOBJECT,
1130+
) and self.related_object_type_id and
1131+
self.related_object_type.app_label == APP_LABEL):
1132+
self._check_recursion()
1133+
1134+
def _check_recursion(self):
1135+
"""
1136+
Check for circular references in object and multiobject fields.
1137+
Raises ValidationError if recursion is detected.
1138+
"""
1139+
# Check if this field points to the same custom object type (self-referential)
1140+
print(f"related_object_type_id: {self.related_object_type_id}, custom_object_type.object_type_id: {self.custom_object_type.object_type_id}")
1141+
breakpoint()
1142+
if self.related_object_type_id == self.custom_object_type.object_type_id:
1143+
return # Self-referential fields are allowed
1144+
1145+
# Get the related custom object type directly from the object_type relationship
1146+
try:
1147+
related_custom_object_type = CustomObjectType.objects.get(object_type=self.related_object_type)
1148+
except CustomObjectType.DoesNotExist:
1149+
return # Not a custom object type, no recursion possible
1150+
1151+
# Check for circular references by traversing the dependency chain
1152+
visited = {self.custom_object_type.id}
1153+
if self._has_circular_reference(related_custom_object_type, visited):
1154+
raise ValidationError(
1155+
{
1156+
"related_object_type": _(
1157+
"Circular reference detected. This field would create a circular dependency "
1158+
"between custom object types."
1159+
)
1160+
}
1161+
)
1162+
1163+
def _has_circular_reference(self, custom_object_type, visited):
1164+
"""
1165+
Recursively check if there's a circular reference by following the dependency chain.
1166+
1167+
Args:
1168+
custom_object_type: The CustomObjectType object to check
1169+
visited: Set of custom object type IDs already visited in this traversal
1170+
1171+
Returns:
1172+
bool: True if a circular reference is detected, False otherwise
1173+
"""
1174+
# If we've already visited this type, we have a cycle
1175+
if custom_object_type.id in visited:
1176+
return True
1177+
1178+
# Add this type to visited set
1179+
visited.add(custom_object_type.id)
1180+
1181+
# Check all object and multiobject fields in this custom object type
1182+
for field in custom_object_type.fields.filter(
1183+
type__in=[
1184+
CustomFieldTypeChoices.TYPE_OBJECT,
1185+
CustomFieldTypeChoices.TYPE_MULTIOBJECT,
1186+
],
1187+
related_object_type__isnull=False,
1188+
related_object_type__app_label=APP_LABEL
1189+
):
1190+
1191+
# Get the related custom object type directly from the object_type relationship
1192+
try:
1193+
next_custom_object_type = CustomObjectType.objects.get(object_type=field.related_object_type)
1194+
except CustomObjectType.DoesNotExist:
1195+
continue
1196+
1197+
# Recursively check this dependency
1198+
if self._has_circular_reference(next_custom_object_type, visited):
1199+
return True
1200+
1201+
return False
1202+
11211203
def serialize(self, value):
11221204
"""
11231205
Prepare a value for storage as JSON data.

netbox_custom_objects/views.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -203,7 +203,7 @@ def _get_dependent_objects(self, obj):
203203

204204
# Find CustomObjectTypeFields that reference this CustomObjectType
205205
referencing_fields = CustomObjectTypeField.objects.filter(
206-
related_object_type=obj.content_type
206+
related_object_type=obj.object_type
207207
)
208208

209209
# Add the CustomObjectTypeFields that reference this CustomObjectType

0 commit comments

Comments
 (0)