diff --git a/docs/release-notes/version-3.1.md b/docs/release-notes/version-3.1.md index c42837b24..89a68423f 100644 --- a/docs/release-notes/version-3.1.md +++ b/docs/release-notes/version-3.1.md @@ -2,6 +2,10 @@ ## v3.1.7 (FUTURE) +### Bug Fixes + +* [#8377](https://github.com/netbox-community/netbox/issues/8377) - Fix calculation of absolute cable lengths when specified in fractional units + --- ## v3.1.6 (2022-01-17) diff --git a/netbox/dcim/migrations/0144_fix_cable_abs_length.py b/netbox/dcim/migrations/0144_fix_cable_abs_length.py new file mode 100644 index 000000000..0da30ffb5 --- /dev/null +++ b/netbox/dcim/migrations/0144_fix_cable_abs_length.py @@ -0,0 +1,31 @@ +from django.db import migrations + +from utilities.utils import to_meters + + +def recalculate_abs_length(apps, schema_editor): + """ + Recalculate absolute lengths for all cables with a length and length unit defined. Fixes + incorrectly calculated values as reported under bug #8377. + """ + Cable = apps.get_model('dcim', 'Cable') + + cables = Cable.objects.filter(length__isnull=False).exclude(length_unit='') + for cable in cables: + cable._abs_length = to_meters(cable.length, cable.length_unit) + + Cable.objects.bulk_update(cables, ['_abs_length'], batch_size=100) + + +class Migration(migrations.Migration): + + dependencies = [ + ('dcim', '0143_remove_primary_for_related_name'), + ] + + operations = [ + migrations.RunPython( + code=recalculate_abs_length, + reverse_code=migrations.RunPython.noop + ), + ] diff --git a/netbox/dcim/tables/cables.py b/netbox/dcim/tables/cables.py index bea2c0adf..9f2c08342 100644 --- a/netbox/dcim/tables/cables.py +++ b/netbox/dcim/tables/cables.py @@ -45,7 +45,7 @@ class CableTable(BaseTable): tenant = TenantColumn() length = TemplateColumn( template_code=CABLE_LENGTH, - order_by='_abs_length' + order_by=('_abs_length', 'length_unit') ) color = ColorColumn() tags = TagColumn( diff --git a/netbox/dcim/tables/template_code.py b/netbox/dcim/tables/template_code.py index ccca32be8..233694a7a 100644 --- a/netbox/dcim/tables/template_code.py +++ b/netbox/dcim/tables/template_code.py @@ -9,7 +9,8 @@ LINKTERMINATION = """ """ CABLE_LENGTH = """ -{% if record.length %}{{ record.length }} {{ record.get_length_unit_display }}{% endif %} +{% load helpers %} +{% if record.length %}{{ record.length|simplify_decimal }} {{ record.length_unit }}{% endif %} """ CABLE_TERMINATION_PARENT = """ diff --git a/netbox/utilities/utils.py b/netbox/utilities/utils.py index ce1f6a111..31e57cd69 100644 --- a/netbox/utilities/utils.py +++ b/netbox/utilities/utils.py @@ -1,9 +1,8 @@ import datetime import json -import urllib from collections import OrderedDict +from decimal import Decimal from itertools import count, groupby -from typing import Any, Dict, List, Tuple from django.core.serializers import serialize from django.db.models import Count, OuterRef, Subquery @@ -195,15 +194,15 @@ def to_meters(length, unit): """ Convert the given length to meters. """ - length = int(length) - if length < 0: - raise ValueError("Length must be a positive integer") + try: + if length < 0: + raise ValueError("Length must be a positive number") + except TypeError: + raise TypeError(f"Invalid value '{length}' for length (must be a number)") valid_units = CableLengthUnitChoices.values() if unit not in valid_units: - raise ValueError( - "Unknown unit {}. Must be one of the following: {}".format(unit, ', '.join(valid_units)) - ) + raise ValueError(f"Unknown unit {unit}. Must be one of the following: {', '.join(valid_units)}") if unit == CableLengthUnitChoices.UNIT_KILOMETER: return length * 1000 @@ -212,11 +211,11 @@ def to_meters(length, unit): if unit == CableLengthUnitChoices.UNIT_CENTIMETER: return length / 100 if unit == CableLengthUnitChoices.UNIT_MILE: - return length * 1609.344 + return length * Decimal(1609.344) if unit == CableLengthUnitChoices.UNIT_FOOT: - return length * 0.3048 + return length * Decimal(0.3048) if unit == CableLengthUnitChoices.UNIT_INCH: - return length * 0.3048 * 12 + return length * Decimal(0.3048) * 12 raise ValueError(f"Unknown unit {unit}. Must be 'km', 'm', 'cm', 'mi', 'ft', or 'in'.")