Repository: netbox-community/netbox

Issue:
Zero length cable should be treated as a 0 length cable and not an unknown length cable
### NetBox version

v3.6.0

### Python version

3.8

### Steps to Reproduce

1. Create all prerequesite objects to have 2 interfaces to connect together (Manufacturer, Type, Role, Device, InterfaceA and InterfaceB)
2. Connect InterfaceC and InterfaceD together using 0 meters

### Expected Behavior

* The cable detail view should show a distance of 0 meters
* A cable trace should show a distance of 0 meters

### Observed Behavior

* Detail view shows placeholder data (--)
* Cable trace considers the distance an unknown distance


Discussion:
I came across this while setting up splice boxes using rear ports and front ports.  When you set a "0" value for the length, the cable is treated as unknown value and the cable trace shows "approximately".

While there is a nominal (1µm perhaps) of distance added to a splice, it doesn't need to be taken into account, however the distance being accurate is important for reporting purposes (did we miss putting in a length or is it just the "0 length" item).

Underlying this is the API, which reports "None" instead of "0" for the CablePath.get_total_length() method.
I think the underlying issue here is that a cable should not be permitted to have a zero length.
I think there are instances (like I said, when you aren't doing actual cables, but documenting "splices" where a 0-length is going to be desireable.  Until we start adding the ability to account for splicing, I think we should allow "0 length" cables and switch to checking for null instead of "not-empty (catches zero as a value)" in 3 places:

`dcim/models/cables.py:193`
`dcim/tables/template_code.py:15`
`dcim/svg/cables.py:254`
`templates/dcim/cable.html:53`
`templates/dcim/trace/cable.html:13`

Right now, _abs_length of the cable gets saves as "none" while the actual cable length is 0.0 m

The API is fine as it returns "0.0 m"

A migration would l likely be required to account for those non-zero values in the code and resave _abs_length
> Until we start adding the ability to account for splicing, I think we should allow "0 length" cables

But a splice isn't a cable, and attempting to model it as one violates our tenet of replicating the real world. You're certainly welcome to submit a feature request proposing this ability, but as far as this bug goes IMO the solution is to invalidate zero-length cables.
I wanted to chime in on this discussion, and I find myself in alignment with @DanSheps regarding the documentation of splice trays and their interconnections. This approach, using "0" and "None," brings clarity to network configurations involving devices with pigtails and splice ports. Let me elaborate further:

Device Configuration:
Devices and modules equipped with pigtails are a common component in fiber optic network setups. Pigtails are essentially short fiber optic cables designed for splicing or connecting other fiber cables. These devices are equipped with front and rear ports of the "splice" type, explicitly meant for splicing optical fibers together.

Splicing in Devices:
When you splice the front and rear ports of these devices together, it effectively results in a "0-length cable." In simpler terms, it means these ports are connected directly, and there is no length of fiber cable between them. This configuration is particularly prevalent in components like splice closures and fiber patch panels with splice trays.

The Significance of "0" and "None":

- `0`: signifies that there is a very short or effectively zero-length cable connecting these ports. It's a practical way to represent the direct splicing of the aforementioned pigtail fibers without any intervening length of fiber cable.
- `None`: We could save `None`, when we're indicating that the length or status of the connection is unknown. This can be useful when we don't have precise information about the connection but want to document its existence.


By adopting this notation, we can maintain consistency in our documentation practices and provide a clear understanding of these specific network configurations. It not only aids in the 80/20 principle of NetBox but also ensures that we accurately represent the physical network infrastructure.
This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. NetBox is governed by a small group of core maintainers which means not all opened issues may receive direct feedback. **Do not** attempt to circumvent this process by "bumping" the issue; doing so will result in its immediate closure and you may be barred from participating in any future discussions. Please see our [contributing guide](https://github.com/netbox-community/netbox/blob/develop/CONTRIBUTING.md).
Given the closure of #13375 having clarity on this point would be useful.

I also see the value in modeling breakout cables using 0-length splices (vs. None).

If a different approach is preferred could we change this issue into a documentation one?
We discussed this on the maintainers call today. Let's proceed with respecting zero-length cables, and make the necessary changes to the length calculation logic.

Resolving patch:
diff --git a/netbox/dcim/migrations/0182_zero_length_cable_fix.py b/netbox/dcim/migrations/0182_zero_length_cable_fix.py
new file mode 100644
--- /dev/null
+++ b/netbox/dcim/migrations/0182_zero_length_cable_fix.py
@@ -0,0 +1,22 @@
+from django.db import migrations
+
+
+def update_cable_lengths(apps, schema_editor):
+    Cable = apps.get_model('dcim', 'Cable')
+
+    # Set the absolute length for any zero-length Cables
+    Cable.objects.filter(length=0).update(_abs_length=0)
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('dcim', '0181_rename_device_role_device_role'),
+    ]
+
+    operations = [
+        migrations.RunPython(
+            code=update_cable_lengths,
+            reverse_code=migrations.RunPython.noop
+        ),
+    ]
diff --git a/netbox/dcim/models/cables.py b/netbox/dcim/models/cables.py
--- a/netbox/dcim/models/cables.py
+++ b/netbox/dcim/models/cables.py
@@ -201,7 +201,7 @@ def save(self, *args, **kwargs):
         _created = self.pk is None
 
         # Store the given length (if any) in meters for use in database ordering
-        if self.length and self.length_unit:
+        if self.length is not None and self.length_unit:
             self._abs_length = to_meters(self.length, self.length_unit)
         else:
             self._abs_length = None
diff --git a/netbox/dcim/svg/cables.py b/netbox/dcim/svg/cables.py
--- a/netbox/dcim/svg/cables.py
+++ b/netbox/dcim/svg/cables.py
@@ -274,7 +274,7 @@ def draw_cable(self, cable, terminations, cable_count=0):
             if cable.type:
                 # Include the cable type in the tooltip
                 description.append(cable.get_type_display())
-            if cable.length and cable.length_unit:
+            if cable.length is not None and cable.length_unit:
                 # Include the cable length in the tooltip
                 description.append(f'{cable.length} {cable.get_length_unit_display()}')
         else:
@@ -285,7 +285,7 @@ def draw_cable(self, cable, terminations, cable_count=0):
             description = []
             if cable.type:
                 labels.append(cable.get_type_display())
-            if cable.length and cable.length_unit:
+            if cable.length is not None and cable.length_unit:
                 # Include the cable length in the tooltip
                 labels.append(f'{cable.length} {cable.get_length_unit_display()}')
 


Test patch: