From cf0f0d7dccb57d0968c88f535667e5705bf9892c Mon Sep 17 00:00:00 2001 From: Arthur Date: Wed, 12 Jun 2024 11:38:17 -0700 Subject: [PATCH] 15106 add wireles link length --- .../api/serializers_/wirelesslinks.py | 4 ++- netbox/wireless/choices.py | 18 +++++++++++ netbox/wireless/filtersets.py | 2 +- netbox/wireless/forms/bulk_edit.py | 16 ++++++++-- netbox/wireless/forms/bulk_import.py | 10 +++++-- netbox/wireless/forms/filtersets.py | 9 ++++++ netbox/wireless/forms/model_forms.py | 11 +++++-- .../migrations/0009_wirelesslink_length.py | 28 +++++++++++++++++ netbox/wireless/models.py | 30 +++++++++++++++++++ netbox/wireless/tables/template_code.py | 4 +++ netbox/wireless/tables/wirelesslink.py | 8 ++++- 11 files changed, 130 insertions(+), 10 deletions(-) create mode 100644 netbox/wireless/migrations/0009_wirelesslink_length.py create mode 100644 netbox/wireless/tables/template_code.py diff --git a/netbox/wireless/api/serializers_/wirelesslinks.py b/netbox/wireless/api/serializers_/wirelesslinks.py index 3a7f88856..9d7821ba0 100644 --- a/netbox/wireless/api/serializers_/wirelesslinks.py +++ b/netbox/wireless/api/serializers_/wirelesslinks.py @@ -21,11 +21,13 @@ class WirelessLinkSerializer(NetBoxModelSerializer): tenant = TenantSerializer(nested=True, required=False, allow_null=True) auth_type = ChoiceField(choices=WirelessAuthTypeChoices, required=False, allow_blank=True) auth_cipher = ChoiceField(choices=WirelessAuthCipherChoices, required=False, allow_blank=True) + length_unit = ChoiceField(choices=WirelessLinkLengthUnitChoices, allow_blank=True, required=False, allow_null=True) class Meta: model = WirelessLink fields = [ 'id', 'url', 'display', 'interface_a', 'interface_b', 'ssid', 'status', 'tenant', 'auth_type', - 'auth_cipher', 'auth_psk', 'description', 'comments', 'tags', 'custom_fields', 'created', 'last_updated', + 'auth_cipher', 'auth_psk', 'length', 'length_unit', 'description', 'comments', 'tags', 'custom_fields', + 'created', 'last_updated', ] brief_fields = ('id', 'url', 'display', 'ssid', 'description') diff --git a/netbox/wireless/choices.py b/netbox/wireless/choices.py index 710cd3a8d..b943765ed 100644 --- a/netbox/wireless/choices.py +++ b/netbox/wireless/choices.py @@ -481,3 +481,21 @@ class WirelessAuthCipherChoices(ChoiceSet): (CIPHER_TKIP, 'TKIP'), (CIPHER_AES, 'AES'), ) + + +class WirelessLinkLengthUnitChoices(ChoiceSet): + + # Metric + UNIT_KILOMETER = 'km' + UNIT_METER = 'm' + + # Imperial + UNIT_MILE = 'mi' + UNIT_FOOT = 'ft' + + CHOICES = ( + (UNIT_KILOMETER, _('Kilometers')), + (UNIT_METER, _('Meters')), + (UNIT_MILE, _('Miles')), + (UNIT_FOOT, _('Feet')), + ) diff --git a/netbox/wireless/filtersets.py b/netbox/wireless/filtersets.py index da66df144..1d9c878e9 100644 --- a/netbox/wireless/filtersets.py +++ b/netbox/wireless/filtersets.py @@ -105,7 +105,7 @@ class WirelessLinkFilterSet(NetBoxModelFilterSet, TenancyFilterSet): class Meta: model = WirelessLink - fields = ('id', 'ssid', 'auth_psk', 'description') + fields = ('id', 'ssid', 'auth_psk', 'length', 'length_unit', 'description') def search(self, queryset, name, value): if not value.strip(): diff --git a/netbox/wireless/forms/bulk_edit.py b/netbox/wireless/forms/bulk_edit.py index 84916e8d9..36a345c2c 100644 --- a/netbox/wireless/forms/bulk_edit.py +++ b/netbox/wireless/forms/bulk_edit.py @@ -125,6 +125,17 @@ class WirelessLinkBulkEditForm(NetBoxModelBulkEditForm): required=False, label=_('Pre-shared key') ) + length = forms.DecimalField( + label=_('Length'), + min_value=0, + required=False + ) + length_unit = forms.ChoiceField( + label=_('Length unit'), + choices=add_blank_choice(WirelessLinkLengthUnitChoices), + required=False, + initial='' + ) description = forms.CharField( label=_('Description'), max_length=200, @@ -135,8 +146,9 @@ class WirelessLinkBulkEditForm(NetBoxModelBulkEditForm): model = WirelessLink fieldsets = ( FieldSet('ssid', 'status', 'tenant', 'description'), - FieldSet('auth_type', 'auth_cipher', 'auth_psk', name=_('Authentication')) + FieldSet('auth_type', 'auth_cipher', 'auth_psk', name=_('Authentication')), + FieldSet('length', 'length_unit', name=_('Attributes')), ) nullable_fields = ( - 'ssid', 'tenant', 'description', 'auth_type', 'auth_cipher', 'auth_psk', 'comments', + 'ssid', 'tenant', 'description', 'auth_type', 'auth_cipher', 'auth_psk', 'length', 'comments', ) diff --git a/netbox/wireless/forms/bulk_import.py b/netbox/wireless/forms/bulk_import.py index 38bc37360..8413cd467 100644 --- a/netbox/wireless/forms/bulk_import.py +++ b/netbox/wireless/forms/bulk_import.py @@ -112,10 +112,16 @@ class WirelessLinkImportForm(NetBoxModelImportForm): required=False, help_text=_('Authentication cipher') ) + length_unit = CSVChoiceField( + label=_('Length unit'), + choices=WirelessLinkLengthUnitChoices, + required=False, + help_text=_('Length unit') + ) class Meta: model = WirelessLink fields = ( - 'interface_a', 'interface_b', 'ssid', 'tenant', 'auth_type', 'auth_cipher', 'auth_psk', 'description', - 'comments', 'tags', + 'interface_a', 'interface_b', 'ssid', 'tenant', 'auth_type', 'auth_cipher', 'auth_psk', + 'length', 'length_unit', 'description', 'comments', 'tags', ) diff --git a/netbox/wireless/forms/filtersets.py b/netbox/wireless/forms/filtersets.py index 2458d7b48..e6c500ddc 100644 --- a/netbox/wireless/forms/filtersets.py +++ b/netbox/wireless/forms/filtersets.py @@ -98,4 +98,13 @@ class WirelessLinkFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm): label=_('Pre-shared key'), required=False ) + length = forms.DecimalField( + label=_('Length'), + required=False, + ) + length_unit = forms.ChoiceField( + label=_('Length unit'), + choices=add_blank_choice(WirelessLinkLengthUnitChoices), + required=False + ) tag = TagFilterField(model) diff --git a/netbox/wireless/forms/model_forms.py b/netbox/wireless/forms/model_forms.py index 05debf8bf..9f39aac96 100644 --- a/netbox/wireless/forms/model_forms.py +++ b/netbox/wireless/forms/model_forms.py @@ -159,7 +159,7 @@ class WirelessLinkForm(TenancyForm, NetBoxModelForm): fieldsets = ( FieldSet('site_a', 'location_a', 'device_a', 'interface_a', name=_('Side A')), FieldSet('site_b', 'location_b', 'device_b', 'interface_b', name=_('Side B')), - FieldSet('status', 'ssid', 'description', 'tags', name=_('Link')), + FieldSet('status', 'ssid', 'length', 'length_unit', 'description', 'tags', name=_('Link')), FieldSet('tenant_group', 'tenant', name=_('Tenancy')), FieldSet('auth_type', 'auth_cipher', 'auth_psk', name=_('Authentication')), ) @@ -168,8 +168,8 @@ class WirelessLinkForm(TenancyForm, NetBoxModelForm): model = WirelessLink fields = [ 'site_a', 'location_a', 'device_a', 'interface_a', 'site_b', 'location_b', 'device_b', 'interface_b', - 'status', 'ssid', 'tenant_group', 'tenant', 'auth_type', 'auth_cipher', 'auth_psk', 'description', - 'comments', 'tags', + 'status', 'ssid', 'tenant_group', 'tenant', 'auth_type', 'auth_cipher', 'auth_psk', + 'length', 'length_unit', 'description', 'comments', 'tags', ] widgets = { 'auth_psk': PasswordInput( @@ -181,3 +181,8 @@ class WirelessLinkForm(TenancyForm, NetBoxModelForm): 'auth_type': 'Type', 'auth_cipher': 'Cipher', } + error_messages = { + 'length': { + 'max_value': _('Maximum length is 32767 (any unit)') + } + } diff --git a/netbox/wireless/migrations/0009_wirelesslink_length.py b/netbox/wireless/migrations/0009_wirelesslink_length.py new file mode 100644 index 000000000..43f522971 --- /dev/null +++ b/netbox/wireless/migrations/0009_wirelesslink_length.py @@ -0,0 +1,28 @@ +# Generated by Django 5.0.6 on 2024-06-12 18:15 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('wireless', '0001_squashed_0008'), + ] + + operations = [ + migrations.AddField( + model_name='wirelesslink', + name='_abs_length', + field=models.DecimalField(blank=True, decimal_places=4, max_digits=10, null=True), + ), + migrations.AddField( + model_name='wirelesslink', + name='length', + field=models.DecimalField(blank=True, decimal_places=2, max_digits=8, null=True), + ), + migrations.AddField( + model_name='wirelesslink', + name='length_unit', + field=models.CharField(blank=True, max_length=50), + ), + ] diff --git a/netbox/wireless/models.py b/netbox/wireless/models.py index 0b114f85f..009e58162 100644 --- a/netbox/wireless/models.py +++ b/netbox/wireless/models.py @@ -6,6 +6,7 @@ from django.utils.translation import gettext_lazy as _ from dcim.choices import LinkStatusChoices from dcim.constants import WIRELESS_IFACE_TYPES from netbox.models import NestedGroupModel, PrimaryModel +from utilities.conversion import to_meters from .choices import * from .constants import * @@ -160,6 +161,26 @@ class WirelessLink(WirelessAuthenticationBase, PrimaryModel): choices=LinkStatusChoices, default=LinkStatusChoices.STATUS_CONNECTED ) + length = models.DecimalField( + verbose_name=_('length'), + max_digits=8, + decimal_places=2, + blank=True, + null=True + ) + length_unit = models.CharField( + verbose_name=_('length unit'), + max_length=50, + choices=WirelessLinkLengthUnitChoices, + blank=True, + ) + # Stores the normalized length (in meters) for database ordering + _abs_length = models.DecimalField( + max_digits=10, + decimal_places=4, + blank=True, + null=True + ) tenant = models.ForeignKey( to='tenancy.Tenant', on_delete=models.PROTECT, @@ -224,6 +245,15 @@ class WirelessLink(WirelessAuthenticationBase, PrimaryModel): }) def save(self, *args, **kwargs): + # Store the given length (if any) in meters for use in database ordering + 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 + + # Clear length_unit if no length is defined + if self.length is None: + self.length_unit = '' # Store the parent Device for the A and B interfaces self._interface_a_device = self.interface_a.device diff --git a/netbox/wireless/tables/template_code.py b/netbox/wireless/tables/template_code.py new file mode 100644 index 000000000..885e5ffc4 --- /dev/null +++ b/netbox/wireless/tables/template_code.py @@ -0,0 +1,4 @@ +WIRELESS_LINK_LENGTH = """ +{% load helpers %} +{% if record.length %}{{ record.length|floatformat:"-2" }} {{ record.length_unit }}{% endif %} +""" diff --git a/netbox/wireless/tables/wirelesslink.py b/netbox/wireless/tables/wirelesslink.py index 7c3b3306b..0c4725517 100644 --- a/netbox/wireless/tables/wirelesslink.py +++ b/netbox/wireless/tables/wirelesslink.py @@ -4,6 +4,7 @@ import django_tables2 as tables from netbox.tables import NetBoxTable, columns from tenancy.tables import TenancyColumnsMixin from wireless.models import * +from .template_code import WIRELESS_LINK_LENGTH __all__ = ( 'WirelessLinkTable', @@ -36,6 +37,10 @@ class WirelessLinkTable(TenancyColumnsMixin, NetBoxTable): verbose_name=_('Interface B'), linkify=True ) + length = columns.TemplateColumn( + template_code=WIRELESS_LINK_LENGTH, + order_by=('_abs_length', 'length_unit') + ) tags = columns.TagColumn( url_name='wireless:wirelesslink_list' ) @@ -44,7 +49,8 @@ class WirelessLinkTable(TenancyColumnsMixin, NetBoxTable): model = WirelessLink fields = ( 'pk', 'id', 'status', 'device_a', 'interface_a', 'device_b', 'interface_b', 'ssid', 'tenant', - 'tenant_group', 'description', 'auth_type', 'auth_cipher', 'auth_psk', 'tags', 'created', 'last_updated', + 'tenant_group', 'length', 'description', 'auth_type', 'auth_cipher', 'auth_psk', 'tags', + 'created', 'last_updated', ) default_columns = ( 'pk', 'id', 'status', 'device_a', 'interface_a', 'device_b', 'interface_b', 'ssid', 'auth_type',