Merge pull request #21837 from netbox-community/21795-update-humanize_speed-to-support-decimal-gbpstbps-output

Closes #21795: Improve humanize_speed formatting for decimal Gbps/Tbps values
This commit is contained in:
bctiemann
2026-04-03 13:06:55 -04:00
committed by GitHub
2 changed files with 124 additions and 14 deletions

View File

@@ -186,26 +186,52 @@ def action_url(parser, token):
return ActionURLNode(model, action, kwargs, asvar)
def _format_speed(speed, divisor, unit):
"""
Format a speed value with a given divisor and unit.
Handles decimal values and strips trailing zeros for clean output.
"""
whole, remainder = divmod(speed, divisor)
if remainder == 0:
return f'{whole} {unit}'
# Divisors are powers of 10, so len(str(divisor)) - 1 matches the decimal precision.
precision = len(str(divisor)) - 1
fraction = f'{remainder:0{precision}d}'.rstrip('0')
return f'{whole}.{fraction} {unit}'
@register.filter()
def humanize_speed(speed):
"""
Humanize speeds given in Kbps. Examples:
Humanize speeds given in Kbps, always using the largest appropriate unit.
1544 => "1.544 Mbps"
100000 => "100 Mbps"
10000000 => "10 Gbps"
Decimal values are displayed when the result is not a whole number;
trailing zeros after the decimal point are stripped for clean output.
Examples:
1_544 => "1.544 Mbps"
100_000 => "100 Mbps"
1_000_000 => "1 Gbps"
2_500_000 => "2.5 Gbps"
10_000_000 => "10 Gbps"
800_000_000 => "800 Gbps"
1_600_000_000 => "1.6 Tbps"
"""
if not speed:
return ''
if speed >= 1000000000 and speed % 1000000000 == 0:
return '{} Tbps'.format(int(speed / 1000000000))
if speed >= 1000000 and speed % 1000000 == 0:
return '{} Gbps'.format(int(speed / 1000000))
if speed >= 1000 and speed % 1000 == 0:
return '{} Mbps'.format(int(speed / 1000))
if speed >= 1000:
return '{} Mbps'.format(float(speed) / 1000)
return '{} Kbps'.format(speed)
speed = int(speed)
if speed >= 1_000_000_000:
return _format_speed(speed, 1_000_000_000, 'Tbps')
if speed >= 1_000_000:
return _format_speed(speed, 1_000_000, 'Gbps')
if speed >= 1_000:
return _format_speed(speed, 1_000, 'Mbps')
return f'{speed} Kbps'
def _humanize_capacity(value, divisor=1000):

View File

@@ -3,7 +3,7 @@ from unittest.mock import patch
from django.test import TestCase, override_settings
from utilities.templatetags.builtins.tags import static_with_params
from utilities.templatetags.helpers import _humanize_capacity
from utilities.templatetags.helpers import _humanize_capacity, humanize_speed
class StaticWithParamsTest(TestCase):
@@ -90,3 +90,87 @@ class HumanizeCapacityTest(TestCase):
def test_default_divisor_is_1000(self):
self.assertEqual(_humanize_capacity(2000), '2.00 GB')
class HumanizeSpeedTest(TestCase):
"""
Test the humanize_speed filter for correct unit selection and decimal formatting.
"""
# Falsy / empty inputs
def test_none(self):
self.assertEqual(humanize_speed(None), '')
def test_zero(self):
self.assertEqual(humanize_speed(0), '')
def test_empty_string(self):
self.assertEqual(humanize_speed(''), '')
# Kbps (below 1000)
def test_kbps(self):
self.assertEqual(humanize_speed(100), '100 Kbps')
def test_kbps_low(self):
self.assertEqual(humanize_speed(1), '1 Kbps')
# Mbps (1,000 999,999)
def test_mbps_whole(self):
self.assertEqual(humanize_speed(100_000), '100 Mbps')
def test_mbps_decimal(self):
self.assertEqual(humanize_speed(1_544), '1.544 Mbps')
def test_mbps_10(self):
self.assertEqual(humanize_speed(10_000), '10 Mbps')
# Gbps (1,000,000 999,999,999)
def test_gbps_whole(self):
self.assertEqual(humanize_speed(1_000_000), '1 Gbps')
def test_gbps_decimal(self):
self.assertEqual(humanize_speed(2_500_000), '2.5 Gbps')
def test_gbps_10(self):
self.assertEqual(humanize_speed(10_000_000), '10 Gbps')
def test_gbps_25(self):
self.assertEqual(humanize_speed(25_000_000), '25 Gbps')
def test_gbps_40(self):
self.assertEqual(humanize_speed(40_000_000), '40 Gbps')
def test_gbps_100(self):
self.assertEqual(humanize_speed(100_000_000), '100 Gbps')
def test_gbps_400(self):
self.assertEqual(humanize_speed(400_000_000), '400 Gbps')
def test_gbps_800(self):
self.assertEqual(humanize_speed(800_000_000), '800 Gbps')
# Tbps (1,000,000,000+)
def test_tbps_whole(self):
self.assertEqual(humanize_speed(1_000_000_000), '1 Tbps')
def test_tbps_decimal(self):
self.assertEqual(humanize_speed(1_600_000_000), '1.6 Tbps')
# Edge cases
def test_string_input(self):
"""Ensure string values are cast to int correctly."""
self.assertEqual(humanize_speed('2500000'), '2.5 Gbps')
def test_non_round_remainder_preserved(self):
"""Ensure fractional parts with interior zeros are preserved."""
self.assertEqual(humanize_speed(1_001_000), '1.001 Gbps')
def test_trailing_zeros_stripped(self):
"""Ensure trailing fractional zeros are stripped (5.500 → 5.5)."""
self.assertEqual(humanize_speed(5_500_000), '5.5 Gbps')