Skip to content

Commit 59d8a23

Browse files
committed
#203 make sure cascade DELETE is applied
1 parent 17f614e commit 59d8a23

File tree

1 file changed

+69
-0
lines changed

1 file changed

+69
-0
lines changed

netbox_custom_objects/models.py

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -543,6 +543,68 @@ def get_model_with_serializer(self):
543543
self.register_custom_object_search_index(model)
544544
return model
545545

546+
def _ensure_fk_constraints(self, model):
547+
"""
548+
Ensure that foreign key constraints are properly created at the database level
549+
for OBJECT type fields with ON DELETE CASCADE. This is necessary because models
550+
are created with managed=False, which may not properly create FK constraints
551+
with CASCADE behavior.
552+
553+
:param model: The model to ensure FK constraints for
554+
"""
555+
# Query all OBJECT type fields for this CustomObjectType
556+
object_fields = self.fields.filter(type=CustomFieldTypeChoices.TYPE_OBJECT)
557+
558+
if not object_fields.exists():
559+
return
560+
561+
table_name = self.get_database_table_name()
562+
563+
with connection.cursor() as cursor:
564+
for field in object_fields:
565+
field_name = field.name
566+
try:
567+
model_field = model._meta.get_field(field_name)
568+
if not (hasattr(model_field, 'remote_field') and model_field.remote_field):
569+
continue
570+
571+
# Get the referenced table
572+
related_model = model_field.remote_field.model
573+
related_table = related_model._meta.db_table
574+
column_name = model_field.column
575+
576+
# Drop existing FK constraint if it exists
577+
# Query for existing constraints
578+
cursor.execute("""
579+
SELECT constraint_name
580+
FROM information_schema.table_constraints
581+
WHERE table_name = %s
582+
AND constraint_type = 'FOREIGN KEY'
583+
AND constraint_name LIKE %s
584+
""", [table_name, f"%{column_name}%"])
585+
586+
for row in cursor.fetchall():
587+
constraint_name = row[0]
588+
cursor.execute(f'ALTER TABLE "{table_name}" DROP CONSTRAINT IF EXISTS "{constraint_name}"')
589+
590+
# Create new FK constraint with ON DELETE CASCADE
591+
constraint_name = f"{table_name}_{column_name}_fk_cascade"
592+
cursor.execute(f"""
593+
ALTER TABLE "{table_name}"
594+
ADD CONSTRAINT "{constraint_name}"
595+
FOREIGN KEY ("{column_name}")
596+
REFERENCES "{related_table}" ("id")
597+
ON DELETE CASCADE
598+
DEFERRABLE INITIALLY DEFERRED
599+
""")
600+
601+
except Exception as e:
602+
# Log the error but continue with other fields
603+
import logging
604+
logger = logging.getLogger('netbox.custom_objects')
605+
logger.warning(f"Failed to ensure FK constraint for {table_name}.{field_name}: {e}")
606+
continue
607+
546608
def create_model(self):
547609
from netbox_custom_objects.api.serializers import get_serializer_class
548610
# Get the model and ensure it's registered
@@ -559,6 +621,9 @@ def create_model(self):
559621
with connection.schema_editor() as schema_editor:
560622
schema_editor.create_model(model)
561623

624+
# Ensure FK constraints are properly created for OBJECT fields
625+
self._ensure_fk_constraints(model)
626+
562627
get_serializer_class(model)
563628
self.register_custom_object_search_index(model)
564629

@@ -1482,6 +1547,10 @@ def save(self, *args, **kwargs):
14821547
# Normal field alteration
14831548
schema_editor.alter_field(model, old_field, model_field)
14841549

1550+
# Ensure FK constraints are properly created for OBJECT fields with CASCADE behavior
1551+
if self.type == CustomFieldTypeChoices.TYPE_OBJECT:
1552+
self.custom_object_type._ensure_fk_constraints(model)
1553+
14851554
# Clear and refresh the model cache for this CustomObjectType when a field is modified
14861555
self.custom_object_type.clear_model_cache(self.custom_object_type.id)
14871556

0 commit comments

Comments
 (0)