1
0
mirror of https://github.com/netbox-community/netbox.git synced 2024-05-10 07:54:54 +00:00

Simplify assignment of new cable terminations

This commit is contained in:
jeremystretch
2022-05-18 15:49:52 -04:00
parent 922916ae99
commit a909ceda84
3 changed files with 67 additions and 56 deletions

View File

@ -29,30 +29,13 @@ class BaseCableConnectionForm(TenancyForm, NetBoxModelForm):
disabled_indicator='_occupied' disabled_indicator='_occupied'
) )
def save(self, commit=True): def save(self, *args, **kwargs):
instance = super().save(commit=commit)
# Create CableTermination instances # Set the A/B terminations on the Cable instance
terminations = [] self.instance.a_terminations = self.cleaned_data['a_terminations']
terminations.extend([ self.instance.b_terminations = self.cleaned_data['b_terminations']
CableTermination(cable=instance, cable_end='A', termination=termination)
for termination in self.cleaned_data['a_terminations']
])
terminations.extend([
CableTermination(cable=instance, cable_end='B', termination=termination)
for termination in self.cleaned_data['b_terminations']
])
if commit: return super().save(*args, **kwargs)
for ct in terminations:
ct.save()
else:
instance._terminations = [
*self.cleaned_data['a_terminations'],
*self.cleaned_data['b_terminations'],
]
return instance
class ConnectCableToDeviceForm(BaseCableConnectionForm): class ConnectCableToDeviceForm(BaseCableConnectionForm):

View File

@ -5,6 +5,7 @@ from django.contrib.contenttypes.models import ContentType
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from django.db import models from django.db import models
from django.db.models import Sum from django.db.models import Sum
from django.dispatch import Signal
from django.urls import reverse from django.urls import reverse
from dcim.choices import * from dcim.choices import *
@ -27,6 +28,9 @@ __all__ = (
) )
trace_paths = Signal()
# #
# Cables # Cables
# #
@ -107,20 +111,10 @@ class Cable(NetBoxModel):
self._orig_status = self.status self._orig_status = self.status
# Assign associated CableTerminations (if any) # Assign associated CableTerminations (if any)
terminations = [] if a_terminations is not None:
if a_terminations and type(a_terminations) is list: self.a_terminations = a_terminations
terminations.extend([ if b_terminations is not None:
CableTermination(cable=self, cable_end='A', termination=t) for t in a_terminations self.b_terminations = b_terminations
])
if b_terminations and type(b_terminations) is list:
terminations.extend([
CableTermination(cable=self, cable_end='B', termination=t) for t in b_terminations
])
if terminations:
assert self.pk is None
self._terminations = terminations
else:
self._terminations = []
@classmethod @classmethod
def from_db(cls, db, field_names, values): def from_db(cls, db, field_names, values):
@ -164,6 +158,7 @@ class Cable(NetBoxModel):
self.length_unit = '' self.length_unit = ''
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
_created = self.pk is None
# Store the given length (if any) in meters for use in database ordering # Store the given length (if any) in meters for use in database ordering
if self.length and self.length_unit: if self.length and self.length_unit:
@ -183,6 +178,32 @@ class Cable(NetBoxModel):
# Update the private pk used in __str__ in case this is a new object (i.e. just got its pk) # Update the private pk used in __str__ in case this is a new object (i.e. just got its pk)
self._pk = self.pk self._pk = self.pk
# Retrieve existing A/B terminations for the Cable
a_terminations = {ct.termination: ct for ct in self.terminations.filter(cable_end='A')}
b_terminations = {ct.termination: ct for ct in self.terminations.filter(cable_end='B')}
# Delete stale CableTerminations
if hasattr(self, 'a_terminations'):
for termination, ct in a_terminations.items():
if termination not in self.a_terminations:
ct.delete()
if hasattr(self, 'b_terminations'):
for termination, ct in b_terminations.items():
if termination not in self.b_terminations:
ct.delete()
# Save new CableTerminations (if any)
if hasattr(self, 'a_terminations'):
for termination in self.a_terminations:
if termination not in a_terminations:
CableTermination(cable=self, cable_end='A', termination=termination).save()
if hasattr(self, 'b_terminations'):
for termination in self.b_terminations:
if termination not in b_terminations:
CableTermination(cable=self, cable_end='B', termination=termination).save()
trace_paths.send(Cable, instance=self, created=_created)
def get_status_color(self): def get_status_color(self):
return LinkStatusChoices.colors.get(self.status) return LinkStatusChoices.colors.get(self.status)
@ -271,6 +292,21 @@ class CableTermination(models.Model):
# f"Incompatible termination types: {self.termination_a_type} and {self.termination_b_type}" # f"Incompatible termination types: {self.termination_a_type} and {self.termination_b_type}"
# ) # )
def save(self, *args, **kwargs):
super().save(*args, **kwargs)
# Set the cable on the terminating object
termination_model = self.termination._meta.model
termination_model.objects.filter(pk=self.termination_id).update(cable=self.cable)
def delete(self, *args, **kwargs):
# Delete the cable association on the terminating object
termination_model = self.termination._meta.model
termination_model.objects.filter(pk=self.termination_id).update(cable=None)
super().delete(*args, **kwargs)
class CablePath(models.Model): class CablePath(models.Model):
""" """

View File

@ -1,12 +1,11 @@
from collections import defaultdict
import logging import logging
from django.contrib.contenttypes.models import ContentType
from django.db.models.signals import post_save, post_delete, pre_delete from django.db.models.signals import post_save, post_delete, pre_delete
from django.dispatch import receiver from django.dispatch import receiver
from .choices import LinkStatusChoices from .choices import LinkStatusChoices
from .models import Cable, CablePath, CableTermination, Device, PathEndpoint, PowerPanel, Rack, Location, VirtualChassis from .models import Cable, CablePath, CableTermination, Device, PathEndpoint, PowerPanel, Rack, Location, VirtualChassis
from .models.cables import trace_paths
from .utils import create_cablepath, rebuild_paths from .utils import create_cablepath, rebuild_paths
@ -69,8 +68,7 @@ def clear_virtualchassis_members(instance, **kwargs):
# Cables # Cables
# #
@receiver(trace_paths, sender=Cable)
@receiver(post_save, sender=Cable)
def update_connected_endpoints(instance, created, raw=False, **kwargs): def update_connected_endpoints(instance, created, raw=False, **kwargs):
""" """
When a Cable is saved, check for and update its two connected endpoints When a Cable is saved, check for and update its two connected endpoints
@ -80,28 +78,22 @@ def update_connected_endpoints(instance, created, raw=False, **kwargs):
logger.debug(f"Skipping endpoint updates for imported cable {instance}") logger.debug(f"Skipping endpoint updates for imported cable {instance}")
return return
# Save any new CableTerminations
CableTermination.objects.bulk_create([
term for term in instance._terminations if not term.pk
])
# Split terminations into A/B sets and save link assignments
# TODO: Update link peers # TODO: Update link peers
_terms = defaultdict(list)
for t in instance._terminations:
if t.termination.cable != instance:
t.termination.cable = instance
t.termination.save()
_terms[t.cable_end].append(t.termination)
# Create/update cable paths # Create/update cable paths
if created: if created:
for terms in _terms.values(): _terms = {
'A': [t.termination for t in instance.terminations.filter(cable_end='A')],
'B': [t.termination for t in instance.terminations.filter(cable_end='B')],
}
for nodes in _terms.values():
# Examine type of first termination to determine object type (all must be the same) # Examine type of first termination to determine object type (all must be the same)
if isinstance(terms[0], PathEndpoint): if not nodes:
create_cablepath(terms) continue
if isinstance(nodes[0], PathEndpoint):
create_cablepath(nodes)
else: else:
rebuild_paths(terms) rebuild_paths(nodes)
elif instance.status != instance._orig_status: elif instance.status != instance._orig_status:
# We currently don't support modifying either termination of an existing Cable. (This # We currently don't support modifying either termination of an existing Cable. (This
# may change in the future.) However, we do need to capture status changes and update # may change in the future.) However, we do need to capture status changes and update