diff --git a/CHANGELOG.md b/CHANGELOG.md index 7841bafb7..c51306372 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ v2.6.1 (FUTURE) ## Bug Fixes * [#3229](https://github.com/digitalocean/netbox/issues/3229) - Limit rack group selection by parent site on racks list +* [#3269](https://github.com/digitalocean/netbox/issues/3269) - Raise validation error when specifying non-existent cable terminations * [#3275](https://github.com/digitalocean/netbox/issues/3275) - Fix error when adding power outlets to a device type * [#3279](https://github.com/digitalocean/netbox/issues/3279) - Reset the PostgreSQL sequence for Tag and TaggedItem IDs * [#3283](https://github.com/digitalocean/netbox/issues/3283) - Fix rack group assignment on PowerFeed CSV import diff --git a/netbox/dcim/models.py b/netbox/dcim/models.py index e1d98a2d4..12270fc3e 100644 --- a/netbox/dcim/models.py +++ b/netbox/dcim/models.py @@ -2747,55 +2747,69 @@ class Cable(ChangeLoggedModel): def clean(self): - if self.termination_a and self.termination_b: + # Validate that termination A exists + try: + self.termination_a_type.model_class().objects.get(pk=self.termination_a_id) + except ObjectDoesNotExist: + raise ValidationError({ + 'termination_a': 'Invalid ID for type {}'.format(self.termination_a_type) + }) - type_a = self.termination_a_type.model - type_b = self.termination_b_type.model + # Validate that termination B exists + try: + self.termination_b_type.model_class().objects.get(pk=self.termination_b_id) + except ObjectDoesNotExist: + raise ValidationError({ + 'termination_b': 'Invalid ID for type {}'.format(self.termination_b_type) + }) - # Check that termination types are compatible - if type_b not in COMPATIBLE_TERMINATION_TYPES.get(type_a): - raise ValidationError("Incompatible termination types: {} and {}".format( - self.termination_a_type, self.termination_b_type - )) + type_a = self.termination_a_type.model + type_b = self.termination_b_type.model - # A termination point cannot be connected to itself - if self.termination_a == self.termination_b: - raise ValidationError("Cannot connect {} to itself".format(self.termination_a_type)) + # Check that termination types are compatible + if type_b not in COMPATIBLE_TERMINATION_TYPES.get(type_a): + raise ValidationError("Incompatible termination types: {} and {}".format( + self.termination_a_type, self.termination_b_type + )) - # A front port cannot be connected to its corresponding rear port - if ( - type_a in ['frontport', 'rearport'] and - type_b in ['frontport', 'rearport'] and - ( - getattr(self.termination_a, 'rear_port', None) == self.termination_b or - getattr(self.termination_b, 'rear_port', None) == self.termination_a - ) - ): - raise ValidationError("A front port cannot be connected to it corresponding rear port") + # A termination point cannot be connected to itself + if self.termination_a == self.termination_b: + raise ValidationError("Cannot connect {} to itself".format(self.termination_a_type)) - # Check for an existing Cable connected to either termination object - if self.termination_a.cable not in (None, self): - raise ValidationError("{} already has a cable attached (#{})".format( - self.termination_a, self.termination_a.cable_id - )) - if self.termination_b.cable not in (None, self): - raise ValidationError("{} already has a cable attached (#{})".format( - self.termination_b, self.termination_b.cable_id - )) + # A front port cannot be connected to its corresponding rear port + if ( + type_a in ['frontport', 'rearport'] and + type_b in ['frontport', 'rearport'] and + ( + getattr(self.termination_a, 'rear_port', None) == self.termination_b or + getattr(self.termination_b, 'rear_port', None) == self.termination_a + ) + ): + raise ValidationError("A front port cannot be connected to it corresponding rear port") - # Virtual interfaces cannot be connected - endpoint_a, endpoint_b, _ = self.get_path_endpoints() - if ( - ( - isinstance(endpoint_a, Interface) and - endpoint_a.type == IFACE_TYPE_VIRTUAL - ) or - ( - isinstance(endpoint_b, Interface) and - endpoint_b.type == IFACE_TYPE_VIRTUAL - ) - ): - raise ValidationError("Cannot connect to a virtual interface") + # Check for an existing Cable connected to either termination object + if self.termination_a.cable not in (None, self): + raise ValidationError("{} already has a cable attached (#{})".format( + self.termination_a, self.termination_a.cable_id + )) + if self.termination_b.cable not in (None, self): + raise ValidationError("{} already has a cable attached (#{})".format( + self.termination_b, self.termination_b.cable_id + )) + + # Virtual interfaces cannot be connected + endpoint_a, endpoint_b, _ = self.get_path_endpoints() + if ( + ( + isinstance(endpoint_a, Interface) and + endpoint_a.type == IFACE_TYPE_VIRTUAL + ) or + ( + isinstance(endpoint_b, Interface) and + endpoint_b.type == IFACE_TYPE_VIRTUAL + ) + ): + raise ValidationError("Cannot connect to a virtual interface") # Validate length and length_unit if self.length is not None and self.length_unit is None: