From de87763572b64b8eba72c5de68c27c451dbd5a57 Mon Sep 17 00:00:00 2001 From: Arthur Hanson Date: Mon, 30 Sep 2024 13:51:05 -0700 Subject: [PATCH] 16547 add distance to API, forms, tables --- docs/models/circuits/circuit.md | 6 ++++++ netbox/circuits/api/serializers_/circuits.py | 4 +++- netbox/circuits/filtersets.py | 2 +- netbox/circuits/forms/bulk_edit.py | 13 +++++++++++++ netbox/circuits/forms/bulk_import.py | 9 ++++++++- netbox/circuits/forms/model_forms.py | 14 ++++++++++++-- netbox/circuits/tables/circuits.py | 5 +++++ netbox/circuits/tables/template_code.py | 4 ++++ netbox/circuits/tests/test_filtersets.py | 15 ++++++++++++--- netbox/templates/circuits/circuit.html | 14 ++++++++++++++ 10 files changed, 78 insertions(+), 8 deletions(-) create mode 100644 netbox/circuits/tables/template_code.py diff --git a/docs/models/circuits/circuit.md b/docs/models/circuits/circuit.md index 19fd8c882..c75e20322 100644 --- a/docs/models/circuits/circuit.md +++ b/docs/models/circuits/circuit.md @@ -36,6 +36,12 @@ The operational status of the circuit. By default, the following statuses are av !!! tip "Custom circuit statuses" Additional circuit statuses may be defined by setting `Circuit.status` under the [`FIELD_CHOICES`](../../configuration/data-validation.md#field_choices) configuration parameter. +### Distance + +!!! info "This field was introduced in NetBox v4.2." + +The distance between the circuit's two endpoints, including a unit designation (e.g. 100 meters or 25 feet). + ### Description A brief description of the circuit. diff --git a/netbox/circuits/api/serializers_/circuits.py b/netbox/circuits/api/serializers_/circuits.py index c605a4bd5..111fa6f87 100644 --- a/netbox/circuits/api/serializers_/circuits.py +++ b/netbox/circuits/api/serializers_/circuits.py @@ -4,6 +4,7 @@ from dcim.api.serializers_.cables import CabledObjectSerializer from dcim.api.serializers_.sites import SiteSerializer from netbox.api.fields import ChoiceField, RelatedObjectCountField from netbox.api.serializers import NetBoxModelSerializer, WritableNestedSerializer +from netbox.choices import DistanceUnitChoices from tenancy.api.serializers_.tenants import TenantSerializer from .providers import ProviderAccountSerializer, ProviderNetworkSerializer, ProviderSerializer @@ -80,13 +81,14 @@ class CircuitSerializer(NetBoxModelSerializer): termination_a = CircuitCircuitTerminationSerializer(read_only=True, allow_null=True) termination_z = CircuitCircuitTerminationSerializer(read_only=True, allow_null=True) assignments = CircuitGroupAssignmentSerializer_(nested=True, many=True, required=False) + distance_unit = ChoiceField(choices=DistanceUnitChoices, allow_blank=True, required=False, allow_null=True) class Meta: model = Circuit fields = [ 'id', 'url', 'display_url', 'display', 'cid', 'provider', 'provider_account', 'type', 'status', 'tenant', 'install_date', 'termination_date', 'commit_rate', 'description', 'termination_a', 'termination_z', - 'comments', 'tags', 'custom_fields', 'created', 'last_updated', 'assignments', + 'distance', 'distance_unit', 'comments', 'tags', 'custom_fields', 'created', 'last_updated', 'assignments', ] brief_fields = ('id', 'url', 'display', 'provider', 'cid', 'description') diff --git a/netbox/circuits/filtersets.py b/netbox/circuits/filtersets.py index c55807c75..ebd1fe28d 100644 --- a/netbox/circuits/filtersets.py +++ b/netbox/circuits/filtersets.py @@ -239,7 +239,7 @@ class CircuitFilterSet(NetBoxModelFilterSet, TenancyFilterSet, ContactModelFilte class Meta: model = Circuit - fields = ('id', 'cid', 'description', 'install_date', 'termination_date', 'commit_rate') + fields = ('id', 'cid', 'description', 'install_date', 'termination_date', 'commit_rate', 'distance', 'distance_unit') def search(self, queryset, name, value): if not value.strip(): diff --git a/netbox/circuits/forms/bulk_edit.py b/netbox/circuits/forms/bulk_edit.py index 3bb50a8d0..5cb7b5d30 100644 --- a/netbox/circuits/forms/bulk_edit.py +++ b/netbox/circuits/forms/bulk_edit.py @@ -5,6 +5,7 @@ from circuits.choices import CircuitCommitRateChoices, CircuitPriorityChoices, C from circuits.models import * from dcim.models import Site from ipam.models import ASN +from netbox.choices import DistanceUnitChoices from netbox.forms import NetBoxModelBulkEditForm from tenancy.models import Tenant from utilities.forms import add_blank_choice @@ -160,6 +161,17 @@ class CircuitBulkEditForm(NetBoxModelBulkEditForm): options=CircuitCommitRateChoices ) ) + distance = forms.DecimalField( + label=_('Distance'), + min_value=0, + required=False + ) + distance_unit = forms.ChoiceField( + label=_('Distance unit'), + choices=add_blank_choice(DistanceUnitChoices), + required=False, + initial='' + ) description = forms.CharField( label=_('Description'), max_length=100, @@ -171,6 +183,7 @@ class CircuitBulkEditForm(NetBoxModelBulkEditForm): fieldsets = ( FieldSet('provider', 'type', 'status', 'description', name=_('Circuit')), FieldSet('provider_account', 'install_date', 'termination_date', 'commit_rate', name=_('Service Parameters')), + FieldSet('distance', 'distance_unit', name=_('Attributes')), FieldSet('tenant', name=_('Tenancy')), ) nullable_fields = ( diff --git a/netbox/circuits/forms/bulk_import.py b/netbox/circuits/forms/bulk_import.py index 1e7b6361a..851f54881 100644 --- a/netbox/circuits/forms/bulk_import.py +++ b/netbox/circuits/forms/bulk_import.py @@ -5,6 +5,7 @@ from django.utils.translation import gettext_lazy as _ from circuits.choices import * from circuits.models import * from dcim.models import Site +from netbox.choices import DistanceUnitChoices from netbox.forms import NetBoxModelImportForm from tenancy.models import Tenant from utilities.forms.fields import CSVChoiceField, CSVModelChoiceField, SlugField @@ -95,6 +96,12 @@ class CircuitImportForm(NetBoxModelImportForm): choices=CircuitStatusChoices, help_text=_('Operational status') ) + distance_unit = CSVChoiceField( + label=_('Distance unit'), + choices=DistanceUnitChoices, + required=False, + help_text=_('Distance unit') + ) tenant = CSVModelChoiceField( label=_('Tenant'), queryset=Tenant.objects.all(), @@ -107,7 +114,7 @@ class CircuitImportForm(NetBoxModelImportForm): model = Circuit fields = [ 'cid', 'provider', 'provider_account', 'type', 'status', 'tenant', 'install_date', 'termination_date', - 'commit_rate', 'description', 'comments', 'tags' + 'commit_rate', 'distance', 'distance_unit', 'description', 'comments', 'tags' ] diff --git a/netbox/circuits/forms/model_forms.py b/netbox/circuits/forms/model_forms.py index 9a54fdccb..0327ce611 100644 --- a/netbox/circuits/forms/model_forms.py +++ b/netbox/circuits/forms/model_forms.py @@ -7,7 +7,7 @@ from ipam.models import ASN from netbox.forms import NetBoxModelForm from tenancy.forms import TenancyForm from utilities.forms.fields import CommentField, DynamicModelChoiceField, DynamicModelMultipleChoiceField, SlugField -from utilities.forms.rendering import FieldSet, TabbedGroups +from utilities.forms.rendering import FieldSet, InlineFields, TabbedGroups from utilities.forms.widgets import DatePicker, NumberWithOptions __all__ = ( @@ -108,7 +108,17 @@ class CircuitForm(TenancyForm, NetBoxModelForm): comments = CommentField() fieldsets = ( - FieldSet('provider', 'provider_account', 'cid', 'type', 'status', 'description', 'tags', name=_('Circuit')), + FieldSet( + 'provider', + 'provider_account', + 'cid', + 'type', + 'status', + InlineFields('distance', 'distance_unit', label=_('Distance')), + 'description', + 'tags', + name=_('Circuit') + ), FieldSet('install_date', 'termination_date', 'commit_rate', name=_('Service Parameters')), FieldSet('tenant_group', 'tenant', name=_('Tenancy')), ) diff --git a/netbox/circuits/tables/circuits.py b/netbox/circuits/tables/circuits.py index eefe2bd22..d5322290f 100644 --- a/netbox/circuits/tables/circuits.py +++ b/netbox/circuits/tables/circuits.py @@ -7,6 +7,7 @@ from tenancy.tables import ContactsColumnMixin, TenancyColumnsMixin from netbox.tables import NetBoxTable, columns from .columns import CommitRateColumn +from .template_code import CIRCUIT_DISTANCE __all__ = ( 'CircuitGroupAssignmentTable', @@ -76,6 +77,10 @@ class CircuitTable(TenancyColumnsMixin, ContactsColumnMixin, NetBoxTable): commit_rate = CommitRateColumn( verbose_name=_('Commit Rate') ) + distance = columns.TemplateColumn( + template_code=CIRCUIT_DISTANCE, + order_by=('_abs_distance') + ) comments = columns.MarkdownColumn( verbose_name=_('Comments') ) diff --git a/netbox/circuits/tables/template_code.py b/netbox/circuits/tables/template_code.py new file mode 100644 index 000000000..27a182637 --- /dev/null +++ b/netbox/circuits/tables/template_code.py @@ -0,0 +1,4 @@ +CIRCUIT_DISTANCE = """ +{% load helpers %} +{% if record.distance %}{{ record.distance|floatformat:"-2" }} {{ record.distance_unit }}{% endif %} +""" diff --git a/netbox/circuits/tests/test_filtersets.py b/netbox/circuits/tests/test_filtersets.py index bb350f0d1..78a6cfe6a 100644 --- a/netbox/circuits/tests/test_filtersets.py +++ b/netbox/circuits/tests/test_filtersets.py @@ -5,6 +5,7 @@ from circuits.filtersets import * from circuits.models import * from dcim.models import Cable, Region, Site, SiteGroup from ipam.models import ASN, RIR +from netbox.choices import DistanceUnitChoices from tenancy.models import Tenant, TenantGroup from utilities.testing import ChangeLoggedFilterSetTests @@ -222,9 +223,9 @@ class CircuitTestCase(TestCase, ChangeLoggedFilterSetTests): ProviderNetwork.objects.bulk_create(provider_networks) circuits = ( - Circuit(provider=providers[0], provider_account=provider_accounts[0], tenant=tenants[0], type=circuit_types[0], cid='Test Circuit 1', install_date='2020-01-01', termination_date='2021-01-01', commit_rate=1000, status=CircuitStatusChoices.STATUS_ACTIVE, description='foobar1'), - Circuit(provider=providers[0], provider_account=provider_accounts[0], tenant=tenants[0], type=circuit_types[0], cid='Test Circuit 2', install_date='2020-01-02', termination_date='2021-01-02', commit_rate=2000, status=CircuitStatusChoices.STATUS_ACTIVE, description='foobar2'), - Circuit(provider=providers[0], provider_account=provider_accounts[1], tenant=tenants[1], type=circuit_types[0], cid='Test Circuit 3', install_date='2020-01-03', termination_date='2021-01-03', commit_rate=3000, status=CircuitStatusChoices.STATUS_PLANNED), + Circuit(provider=providers[0], provider_account=provider_accounts[0], tenant=tenants[0], type=circuit_types[0], cid='Test Circuit 1', install_date='2020-01-01', termination_date='2021-01-01', commit_rate=1000, status=CircuitStatusChoices.STATUS_ACTIVE, description='foobar1', distance=10, distance_unit=DistanceUnitChoices.UNIT_FOOT), + Circuit(provider=providers[0], provider_account=provider_accounts[0], tenant=tenants[0], type=circuit_types[0], cid='Test Circuit 2', install_date='2020-01-02', termination_date='2021-01-02', commit_rate=2000, status=CircuitStatusChoices.STATUS_ACTIVE, description='foobar2', distance=20, distance_unit=DistanceUnitChoices.UNIT_METER), + Circuit(provider=providers[0], provider_account=provider_accounts[1], tenant=tenants[1], type=circuit_types[0], cid='Test Circuit 3', install_date='2020-01-03', termination_date='2021-01-03', commit_rate=3000, status=CircuitStatusChoices.STATUS_PLANNED, description='foobar2', distance=30, distance_unit=DistanceUnitChoices.UNIT_METER), Circuit(provider=providers[1], provider_account=provider_accounts[1], tenant=tenants[1], type=circuit_types[1], cid='Test Circuit 4', install_date='2020-01-04', termination_date='2021-01-04', commit_rate=4000, status=CircuitStatusChoices.STATUS_PLANNED), Circuit(provider=providers[1], provider_account=provider_accounts[2], tenant=tenants[2], type=circuit_types[1], cid='Test Circuit 5', install_date='2020-01-05', termination_date='2021-01-05', commit_rate=5000, status=CircuitStatusChoices.STATUS_OFFLINE), Circuit(provider=providers[1], provider_account=provider_accounts[2], tenant=tenants[2], type=circuit_types[1], cid='Test Circuit 6', install_date='2020-01-06', termination_date='2021-01-06', commit_rate=6000, status=CircuitStatusChoices.STATUS_OFFLINE), @@ -289,6 +290,14 @@ class CircuitTestCase(TestCase, ChangeLoggedFilterSetTests): params = {'status': [CircuitStatusChoices.STATUS_ACTIVE, CircuitStatusChoices.STATUS_PLANNED]} self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4) + def test_distance(self): + params = {'distance': [10, 20]} + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) + + def test_distance_unit(self): + params = {'distance_unit': DistanceUnitChoices.UNIT_FOOT} + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1) + def test_description(self): params = {'description': ['foobar1', 'foobar2']} self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) diff --git a/netbox/templates/circuits/circuit.html b/netbox/templates/circuits/circuit.html index 5c2442b90..c21ab5ad7 100644 --- a/netbox/templates/circuits/circuit.html +++ b/netbox/templates/circuits/circuit.html @@ -34,6 +34,20 @@ {% trans "Status" %} {% badge object.get_status_display bg_color=object.get_status_color %} + + {% trans "Description" %} + {{ object.description|placeholder }} + + + {% trans "Distance" %} + + {% if object.distance is not None %} + {{ object.distance|floatformat }} {{ object.get_distance_unit_display }} + {% else %} + {{ ''|placeholder }} + {% endif %} + + {% trans "Tenant" %}