|
@@ -1,5 +1,6 @@
|
|
|
import itertools
|
|
import itertools
|
|
|
import logging
|
|
import logging
|
|
|
|
|
+import threading
|
|
|
from collections import Counter
|
|
from collections import Counter
|
|
|
|
|
|
|
|
from django.contrib.contenttypes.fields import GenericForeignKey
|
|
from django.contrib.contenttypes.fields import GenericForeignKey
|
|
@@ -76,6 +77,11 @@ class Cable(PrimaryModel):
|
|
|
"""
|
|
"""
|
|
|
A physical connection between two endpoints.
|
|
A physical connection between two endpoints.
|
|
|
"""
|
|
"""
|
|
|
|
|
+ # Per-thread tracking of Cable PKs currently in delete(); referenced by
|
|
|
|
|
+ # dcim.signals.nullify_connected_endpoints to skip per-CableTermination
|
|
|
|
|
+ # cable path retracing during cascade (retrace_cable_paths handles it once).
|
|
|
|
|
+ _deletion_tracking = threading.local()
|
|
|
|
|
+
|
|
|
type = models.CharField(
|
|
type = models.CharField(
|
|
|
verbose_name=_('type'),
|
|
verbose_name=_('type'),
|
|
|
max_length=50,
|
|
max_length=50,
|
|
@@ -343,6 +349,26 @@ class Cable(PrimaryModel):
|
|
|
except UnsupportedCablePath as e:
|
|
except UnsupportedCablePath as e:
|
|
|
raise AbortRequest(e)
|
|
raise AbortRequest(e)
|
|
|
|
|
|
|
|
|
|
+ def delete(self, *args, **kwargs):
|
|
|
|
|
+ # Track this Cable as being deleted so the post_delete signal handler
|
|
|
|
|
+ # for cascaded CableTerminations can skip redundant path retracing;
|
|
|
|
|
+ # retrace_cable_paths() will retrace each affected path once after the
|
|
|
|
|
+ # Cable itself is deleted. Cache the PK locally because super().delete()
|
|
|
|
|
+ # clears self.pk before the finally block runs. The tracking set lives
|
|
|
|
|
+ # on a threading.local() to isolate concurrent deletions across threads.
|
|
|
|
|
+ if not hasattr(Cable._deletion_tracking, 'pks'):
|
|
|
|
|
+ Cable._deletion_tracking.pks = set()
|
|
|
|
|
+ pk = self.pk
|
|
|
|
|
+ Cable._deletion_tracking.pks.add(pk)
|
|
|
|
|
+ try:
|
|
|
|
|
+ return super().delete(*args, **kwargs)
|
|
|
|
|
+ finally:
|
|
|
|
|
+ Cable._deletion_tracking.pks.discard(pk)
|
|
|
|
|
+
|
|
|
|
|
+ @classmethod
|
|
|
|
|
+ def _is_being_deleted(cls, pk):
|
|
|
|
|
+ return pk in getattr(cls._deletion_tracking, 'pks', ())
|
|
|
|
|
+
|
|
|
def clone(self):
|
|
def clone(self):
|
|
|
"""
|
|
"""
|
|
|
Return attributes suitable for cloning this cable.
|
|
Return attributes suitable for cloning this cable.
|