Repository: netbox-community/netbox

Issue:
Add support for a deprecated status on cables
### Environment
* NetBox version: 2.5.12

### Proposed Functionality
Add a deprecated status to cables.

### Use Case
Used in the workflow of cable management.

Racks provide clarity on physically adding and removing through the states reserved and deprecated. Devices have planned and decommissioning. The cable planned state can't tell you if a cable needs adding or removing.

We generate work orders for datacenter personnel by querying Netbox's API and creating tickets with the data parsed into tasks. This way we integrate our source of truth into projects and force callbacks to the Netbox system. It would be great to be able to integrate our cable management into this as well.

### Database Changes
None

### External Dependencies
None


Discussion:
Just to add a little information for discussion:

Currently cable status is a boolean (0,1) value.  With 0 being planned and 1 being connected.

This would obviously require a change to that.  Are there any other potential status values that might ne desired?


(Keep in mind, I am not saying this is accepted or rejected, just for more informational purposes)
My apologies for missing that detail. That means accepting this request would lead to a change in functionality, instead of an addition.

We're requesting this because interfaces and cables are ineligible for custom fields. Tags seem an inappropriate alternative, but we could at least have a working solution with them if this request were to be rejected.

If the request is accepted, another desired state might be "failed", to indicate a cable needs replacing. The device model uses this as well.
Aside: Device has a recently-added "Decommissioning" status, which also captures the idea of "scheduled to be removed".
> Aside: Device has a recently-added "Decommissioning" status, which also captures the idea of "scheduled to be removed".

I would agree with this. It would make more sense to re-use a term used elsewhere for consistency.

Now, going against my own reccomendation above, I would also reccomend 'Inactive' as another valid choice to be included. My company often plans out patch panels for future expansion, and the wiring between the patch panels sits dark for months or even years, simply ready to be used when needed.

Resolving patch:
diff --git a/netbox/dcim/choices.py b/netbox/dcim/choices.py
--- a/netbox/dcim/choices.py
+++ b/netbox/dcim/choices.py
@@ -973,10 +973,12 @@ class CableStatusChoices(ChoiceSet):
 
     STATUS_CONNECTED = 'connected'
     STATUS_PLANNED = 'planned'
+    STATUS_DECOMMISSIONING = 'decommissioning'
 
     CHOICES = (
         (STATUS_CONNECTED, 'Connected'),
         (STATUS_PLANNED, 'Planned'),
+        (STATUS_DECOMMISSIONING, 'Decommissioning'),
     )
 
     LEGACY_MAP = {
diff --git a/netbox/dcim/constants.py b/netbox/dcim/constants.py
--- a/netbox/dcim/constants.py
+++ b/netbox/dcim/constants.py
@@ -61,13 +61,10 @@
 # Cabling and connections
 #
 
-# TODO: Replace with CableStatusChoices?
 # Console/power/interface connection statuses
-CONNECTION_STATUS_PLANNED = False
-CONNECTION_STATUS_CONNECTED = True
 CONNECTION_STATUS_CHOICES = [
-    [CONNECTION_STATUS_PLANNED, 'Planned'],
-    [CONNECTION_STATUS_CONNECTED, 'Connected'],
+    [False, 'Not Connected'],
+    [True, 'Connected'],
 ]
 
 # Cable endpoint types
diff --git a/netbox/dcim/models/__init__.py b/netbox/dcim/models/__init__.py
--- a/netbox/dcim/models/__init__.py
+++ b/netbox/dcim/models/__init__.py
@@ -1956,6 +1956,7 @@ class Cable(ChangeLoggedModel):
     STATUS_CLASS_MAP = {
         CableStatusChoices.STATUS_CONNECTED: 'success',
         CableStatusChoices.STATUS_PLANNED: 'info',
+        CableStatusChoices.STATUS_DECOMMISSIONING: 'warning',
     }
 
     class Meta:
@@ -2116,14 +2117,14 @@ def get_path_endpoints(self):
         b_path = self.termination_a.trace()
 
         # Determine overall path status (connected or planned)
-        if self.status == CableStatusChoices.STATUS_PLANNED:
-            path_status = CONNECTION_STATUS_PLANNED
-        else:
-            path_status = CONNECTION_STATUS_CONNECTED
+        if self.status == CableStatusChoices.STATUS_CONNECTED:
+            path_status = True
             for segment in a_path[1:] + b_path[1:]:
-                if segment[1] is None or segment[1].status == CableStatusChoices.STATUS_PLANNED:
-                    path_status = CONNECTION_STATUS_PLANNED
+                if segment[1] is None or segment[1].status != CableStatusChoices.STATUS_CONNECTED:
+                    path_status = False
                     break
+        else:
+            path_status = False
 
         a_endpoint = a_path[-1][2]
         b_endpoint = b_path[-1][2]


Test patch:
diff --git a/netbox/dcim/tests/test_models.py b/netbox/dcim/tests/test_models.py
--- a/netbox/dcim/tests/test_models.py
+++ b/netbox/dcim/tests/test_models.py
@@ -2,7 +2,6 @@
 from django.test import TestCase
 
 from dcim.choices import *
-from dcim.constants import CONNECTION_STATUS_CONNECTED, CONNECTION_STATUS_PLANNED
 from dcim.models import *
 from tenancy.models import Tenant
 
@@ -522,14 +521,14 @@ def test_path_completion(self):
         cable3.save()
         interface1 = Interface.objects.get(pk=self.interface1.pk)
         self.assertEqual(interface1.connected_endpoint, self.interface2)
-        self.assertEqual(interface1.connection_status, CONNECTION_STATUS_PLANNED)
+        self.assertFalse(interface1.connection_status)
 
         # Switch third segment from planned to connected
         cable3.status = CableStatusChoices.STATUS_CONNECTED
         cable3.save()
         interface1 = Interface.objects.get(pk=self.interface1.pk)
         self.assertEqual(interface1.connected_endpoint, self.interface2)
-        self.assertEqual(interface1.connection_status, CONNECTION_STATUS_CONNECTED)
+        self.assertTrue(interface1.connection_status)
 
     def test_path_teardown(self):
 
@@ -542,7 +541,7 @@ def test_path_teardown(self):
         cable3.save()
         interface1 = Interface.objects.get(pk=self.interface1.pk)
         self.assertEqual(interface1.connected_endpoint, self.interface2)
-        self.assertEqual(interface1.connection_status, CONNECTION_STATUS_CONNECTED)
+        self.assertTrue(interface1.connection_status)
 
         # Remove a cable
         cable2.delete()