Przeglądaj źródła

15106 Add Length Field to Wireless Link (#16528)

* 15106 add wireles link length

* 15106 add wireles link length

* 15106 add wireless link length

* 15106 add tests

* 15106 rename length -> distance

* 15106 rename length -> distance

* 15106 review comments

* 15106 review comments

* 15106 fix form

* 15106 length -> distance
Arthur Hanson 1 rok temu
rodzic
commit
91dcecbd07

+ 4 - 0
docs/models/wireless/wirelesslink.md

@@ -40,3 +40,7 @@ The security cipher used to apply wireless authentication. Options include:
 ### Pre-Shared Key
 ### Pre-Shared Key
 
 
 The security key configured on each client to grant access to the secured wireless LAN. This applies only to certain authentication types.
 The security key configured on each client to grant access to the secured wireless LAN. This applies only to certain authentication types.
+
+### Distance
+
+The numeric distance of the link, including a unit designation (e.g. 100 meters or 25 feet).

+ 0 - 5
netbox/dcim/forms/model_forms.py

@@ -656,11 +656,6 @@ class CableForm(TenancyForm, NetBoxModelForm):
             'a_terminations_type', 'b_terminations_type', 'type', 'status', 'tenant_group', 'tenant', 'label', 'color',
             'a_terminations_type', 'b_terminations_type', 'type', 'status', 'tenant_group', 'tenant', 'label', 'color',
             'length', 'length_unit', 'description', 'comments', 'tags',
             'length', 'length_unit', 'description', 'comments', 'tags',
         ]
         ]
-        error_messages = {
-            'length': {
-                'max_value': _('Maximum length is 32767 (any unit)')
-            }
-        }
 
 
 
 
 class PowerPanelForm(NetBoxModelForm):
 class PowerPanelForm(NetBoxModelForm):

+ 2 - 0
netbox/dcim/svg/cables.py

@@ -393,6 +393,8 @@ class CableTraceSVG:
                         labels = [f"{cable}"] if len(links) > 2 else [f"Wireless {cable}", cable.get_status_display()]
                         labels = [f"{cable}"] if len(links) > 2 else [f"Wireless {cable}", cable.get_status_display()]
                         if cable.ssid:
                         if cable.ssid:
                             description.append(f"{cable.ssid}")
                             description.append(f"{cable.ssid}")
+                        if cable.distance and cable.distance_unit:
+                            description.append(f"{cable.distance} {cable.get_distance_unit_display()}")
                         near = [term for term in near_terminations if term.object == cable.interface_a]
                         near = [term for term in near_terminations if term.object == cable.interface_a]
                         far = [term for term in far_terminations if term.object == cable.interface_b]
                         far = [term for term in far_terminations if term.object == cable.interface_b]
                         if not (near and far):
                         if not (near and far):

+ 1 - 1
netbox/dcim/tables/cables.py

@@ -109,7 +109,7 @@ class CableTable(TenancyColumnsMixin, NetBoxTable):
     status = columns.ChoiceFieldColumn()
     status = columns.ChoiceFieldColumn()
     length = columns.TemplateColumn(
     length = columns.TemplateColumn(
         template_code=CABLE_LENGTH,
         template_code=CABLE_LENGTH,
-        order_by=('_abs_length', 'length_unit')
+        order_by=('_abs_length')
     )
     )
     color = columns.ColorColumn()
     color = columns.ColorColumn()
     comments = columns.MarkdownColumn()
     comments = columns.MarkdownColumn()

+ 10 - 0
netbox/templates/wireless/wirelesslink.html

@@ -34,6 +34,16 @@
             <th scope="row">{% trans "Description" %}</th>
             <th scope="row">{% trans "Description" %}</th>
             <td>{{ object.description|placeholder }}</td>
             <td>{{ object.description|placeholder }}</td>
           </tr>
           </tr>
+          <tr>
+            <th scope="row">{% trans "Distance" %}</th>
+            <td>
+              {% if object.distance is not None %}
+                {{ object.distance|floatformat }} {{ object.get_distance_unit_display }}
+              {% else %}
+                {{ ''|placeholder }}
+              {% endif %}
+            </td>
+          </tr>
         </table>
         </table>
       </div>
       </div>
       {% include 'inc/panels/tags.html' %}
       {% include 'inc/panels/tags.html' %}

+ 3 - 1
netbox/wireless/api/serializers_/wirelesslinks.py

@@ -21,11 +21,13 @@ class WirelessLinkSerializer(NetBoxModelSerializer):
     tenant = TenantSerializer(nested=True, required=False, allow_null=True)
     tenant = TenantSerializer(nested=True, required=False, allow_null=True)
     auth_type = ChoiceField(choices=WirelessAuthTypeChoices, required=False, allow_blank=True)
     auth_type = ChoiceField(choices=WirelessAuthTypeChoices, required=False, allow_blank=True)
     auth_cipher = ChoiceField(choices=WirelessAuthCipherChoices, required=False, allow_blank=True)
     auth_cipher = ChoiceField(choices=WirelessAuthCipherChoices, required=False, allow_blank=True)
+    distance_unit = ChoiceField(choices=WirelessLinkDistanceUnitChoices, allow_blank=True, required=False, allow_null=True)
 
 
     class Meta:
     class Meta:
         model = WirelessLink
         model = WirelessLink
         fields = [
         fields = [
             'id', 'url', 'display', 'interface_a', 'interface_b', 'ssid', 'status', 'tenant', 'auth_type',
             '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', 'distance', 'distance_unit', 'description', 'comments', 'tags', 'custom_fields',
+            'created', 'last_updated',
         ]
         ]
         brief_fields = ('id', 'url', 'display', 'ssid', 'description')
         brief_fields = ('id', 'url', 'display', 'ssid', 'description')

+ 18 - 0
netbox/wireless/choices.py

@@ -481,3 +481,21 @@ class WirelessAuthCipherChoices(ChoiceSet):
         (CIPHER_TKIP, 'TKIP'),
         (CIPHER_TKIP, 'TKIP'),
         (CIPHER_AES, 'AES'),
         (CIPHER_AES, 'AES'),
     )
     )
+
+
+class WirelessLinkDistanceUnitChoices(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')),
+    )

+ 1 - 1
netbox/wireless/filtersets.py

@@ -105,7 +105,7 @@ class WirelessLinkFilterSet(NetBoxModelFilterSet, TenancyFilterSet):
 
 
     class Meta:
     class Meta:
         model = WirelessLink
         model = WirelessLink
-        fields = ('id', 'ssid', 'auth_psk', 'description')
+        fields = ('id', 'ssid', 'auth_psk', 'distance', 'distance_unit', 'description')
 
 
     def search(self, queryset, name, value):
     def search(self, queryset, name, value):
         if not value.strip():
         if not value.strip():

+ 14 - 2
netbox/wireless/forms/bulk_edit.py

@@ -125,6 +125,17 @@ class WirelessLinkBulkEditForm(NetBoxModelBulkEditForm):
         required=False,
         required=False,
         label=_('Pre-shared key')
         label=_('Pre-shared key')
     )
     )
+    distance = forms.DecimalField(
+        label=_('Distance'),
+        min_value=0,
+        required=False
+    )
+    distance_unit = forms.ChoiceField(
+        label=_('Distance unit'),
+        choices=add_blank_choice(WirelessLinkDistanceUnitChoices),
+        required=False,
+        initial=''
+    )
     description = forms.CharField(
     description = forms.CharField(
         label=_('Description'),
         label=_('Description'),
         max_length=200,
         max_length=200,
@@ -135,8 +146,9 @@ class WirelessLinkBulkEditForm(NetBoxModelBulkEditForm):
     model = WirelessLink
     model = WirelessLink
     fieldsets = (
     fieldsets = (
         FieldSet('ssid', 'status', 'tenant', 'description'),
         FieldSet('ssid', 'status', 'tenant', 'description'),
-        FieldSet('auth_type', 'auth_cipher', 'auth_psk', name=_('Authentication'))
+        FieldSet('auth_type', 'auth_cipher', 'auth_psk', name=_('Authentication')),
+        FieldSet('distance', 'distance_unit', name=_('Attributes')),
     )
     )
     nullable_fields = (
     nullable_fields = (
-        'ssid', 'tenant', 'description', 'auth_type', 'auth_cipher', 'auth_psk', 'comments',
+        'ssid', 'tenant', 'description', 'auth_type', 'auth_cipher', 'auth_psk', 'distance', 'comments',
     )
     )

+ 8 - 2
netbox/wireless/forms/bulk_import.py

@@ -112,10 +112,16 @@ class WirelessLinkImportForm(NetBoxModelImportForm):
         required=False,
         required=False,
         help_text=_('Authentication cipher')
         help_text=_('Authentication cipher')
     )
     )
+    distance_unit = CSVChoiceField(
+        label=_('Distance unit'),
+        choices=WirelessLinkDistanceUnitChoices,
+        required=False,
+        help_text=_('Distance unit')
+    )
 
 
     class Meta:
     class Meta:
         model = WirelessLink
         model = WirelessLink
         fields = (
         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',
+            'distance', 'distance_unit', 'description', 'comments', 'tags',
         )
         )

+ 10 - 1
netbox/wireless/forms/filtersets.py

@@ -71,7 +71,7 @@ class WirelessLinkFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
     model = WirelessLink
     model = WirelessLink
     fieldsets = (
     fieldsets = (
         FieldSet('q', 'filter_id', 'tag'),
         FieldSet('q', 'filter_id', 'tag'),
-        FieldSet('ssid', 'status', name=_('Attributes')),
+        FieldSet('ssid', 'status', 'distance', 'distance_unit', name=_('Attributes')),
         FieldSet('tenant_group_id', 'tenant_id', name=_('Tenant')),
         FieldSet('tenant_group_id', 'tenant_id', name=_('Tenant')),
         FieldSet('auth_type', 'auth_cipher', 'auth_psk', name=_('Authentication')),
         FieldSet('auth_type', 'auth_cipher', 'auth_psk', name=_('Authentication')),
     )
     )
@@ -98,4 +98,13 @@ class WirelessLinkFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
         label=_('Pre-shared key'),
         label=_('Pre-shared key'),
         required=False
         required=False
     )
     )
+    distance = forms.DecimalField(
+        label=_('Distance'),
+        required=False,
+    )
+    distance_unit = forms.ChoiceField(
+        label=_('Distance unit'),
+        choices=add_blank_choice(WirelessLinkDistanceUnitChoices),
+        required=False
+    )
     tag = TagFilterField(model)
     tag = TagFilterField(model)

+ 3 - 3
netbox/wireless/forms/model_forms.py

@@ -159,7 +159,7 @@ class WirelessLinkForm(TenancyForm, NetBoxModelForm):
     fieldsets = (
     fieldsets = (
         FieldSet('site_a', 'location_a', 'device_a', 'interface_a', name=_('Side A')),
         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('site_b', 'location_b', 'device_b', 'interface_b', name=_('Side B')),
-        FieldSet('status', 'ssid', 'description', 'tags', name=_('Link')),
+        FieldSet('status', 'ssid', 'distance', 'distance_unit', 'description', 'tags', name=_('Link')),
         FieldSet('tenant_group', 'tenant', name=_('Tenancy')),
         FieldSet('tenant_group', 'tenant', name=_('Tenancy')),
         FieldSet('auth_type', 'auth_cipher', 'auth_psk', name=_('Authentication')),
         FieldSet('auth_type', 'auth_cipher', 'auth_psk', name=_('Authentication')),
     )
     )
@@ -168,8 +168,8 @@ class WirelessLinkForm(TenancyForm, NetBoxModelForm):
         model = WirelessLink
         model = WirelessLink
         fields = [
         fields = [
             'site_a', 'location_a', 'device_a', 'interface_a', 'site_b', 'location_b', 'device_b', 'interface_b',
             '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',
+            'distance', 'distance_unit', 'description', 'comments', 'tags',
         ]
         ]
         widgets = {
         widgets = {
             'auth_psk': PasswordInput(
             'auth_psk': PasswordInput(

+ 28 - 0
netbox/wireless/migrations/0009_wirelesslink_distance.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_distance',
+            field=models.DecimalField(blank=True, decimal_places=4, max_digits=10, null=True),
+        ),
+        migrations.AddField(
+            model_name='wirelesslink',
+            name='distance',
+            field=models.DecimalField(blank=True, decimal_places=2, max_digits=8, null=True),
+        ),
+        migrations.AddField(
+            model_name='wirelesslink',
+            name='distance_unit',
+            field=models.CharField(blank=True, max_length=50),
+        ),
+    ]

+ 35 - 0
netbox/wireless/models.py

@@ -6,6 +6,7 @@ from django.utils.translation import gettext_lazy as _
 from dcim.choices import LinkStatusChoices
 from dcim.choices import LinkStatusChoices
 from dcim.constants import WIRELESS_IFACE_TYPES
 from dcim.constants import WIRELESS_IFACE_TYPES
 from netbox.models import NestedGroupModel, PrimaryModel
 from netbox.models import NestedGroupModel, PrimaryModel
+from utilities.conversion import to_meters
 from .choices import *
 from .choices import *
 from .constants import *
 from .constants import *
 
 
@@ -160,6 +161,26 @@ class WirelessLink(WirelessAuthenticationBase, PrimaryModel):
         choices=LinkStatusChoices,
         choices=LinkStatusChoices,
         default=LinkStatusChoices.STATUS_CONNECTED
         default=LinkStatusChoices.STATUS_CONNECTED
     )
     )
+    distance = models.DecimalField(
+        verbose_name=_('distance'),
+        max_digits=8,
+        decimal_places=2,
+        blank=True,
+        null=True
+    )
+    distance_unit = models.CharField(
+        verbose_name=_('distance unit'),
+        max_length=50,
+        choices=WirelessLinkDistanceUnitChoices,
+        blank=True,
+    )
+    # Stores the normalized distance (in meters) for database ordering
+    _abs_distance = models.DecimalField(
+        max_digits=10,
+        decimal_places=4,
+        blank=True,
+        null=True
+    )
     tenant = models.ForeignKey(
     tenant = models.ForeignKey(
         to='tenancy.Tenant',
         to='tenancy.Tenant',
         on_delete=models.PROTECT,
         on_delete=models.PROTECT,
@@ -208,6 +229,11 @@ class WirelessLink(WirelessAuthenticationBase, PrimaryModel):
         return LinkStatusChoices.colors.get(self.status)
         return LinkStatusChoices.colors.get(self.status)
 
 
     def clean(self):
     def clean(self):
+        super().clean()
+
+        # Validate distance and distance_unit
+        if self.distance is not None and not self.distance_unit:
+            raise ValidationError(_("Must specify a unit when setting a wireless distance"))
 
 
         # Validate interface types
         # Validate interface types
         if self.interface_a.type not in WIRELESS_IFACE_TYPES:
         if self.interface_a.type not in WIRELESS_IFACE_TYPES:
@@ -224,6 +250,15 @@ class WirelessLink(WirelessAuthenticationBase, PrimaryModel):
             })
             })
 
 
     def save(self, *args, **kwargs):
     def save(self, *args, **kwargs):
+        # Store the given distance (if any) in meters for use in database ordering
+        if self.distance is not None and self.distance_unit:
+            self._abs_distance = to_meters(self.distance, self.distance_unit)
+        else:
+            self._abs_distance = None
+
+        # Clear distance_unit if no distance is defined
+        if self.distance is None:
+            self.distance_unit = ''
 
 
         # Store the parent Device for the A and B interfaces
         # Store the parent Device for the A and B interfaces
         self._interface_a_device = self.interface_a.device
         self._interface_a_device = self.interface_a.device

+ 4 - 0
netbox/wireless/tables/template_code.py

@@ -0,0 +1,4 @@
+WIRELESS_LINK_DISTANCE = """
+{% load helpers %}
+{% if record.distance %}{{ record.distance|floatformat:"-2" }} {{ record.distance_unit }}{% endif %}
+"""

+ 7 - 1
netbox/wireless/tables/wirelesslink.py

@@ -4,6 +4,7 @@ import django_tables2 as tables
 from netbox.tables import NetBoxTable, columns
 from netbox.tables import NetBoxTable, columns
 from tenancy.tables import TenancyColumnsMixin
 from tenancy.tables import TenancyColumnsMixin
 from wireless.models import *
 from wireless.models import *
+from .template_code import WIRELESS_LINK_DISTANCE
 
 
 __all__ = (
 __all__ = (
     'WirelessLinkTable',
     'WirelessLinkTable',
@@ -36,6 +37,10 @@ class WirelessLinkTable(TenancyColumnsMixin, NetBoxTable):
         verbose_name=_('Interface B'),
         verbose_name=_('Interface B'),
         linkify=True
         linkify=True
     )
     )
+    distance = columns.TemplateColumn(
+        template_code=WIRELESS_LINK_DISTANCE,
+        order_by=('_abs_distance')
+    )
     tags = columns.TagColumn(
     tags = columns.TagColumn(
         url_name='wireless:wirelesslink_list'
         url_name='wireless:wirelesslink_list'
     )
     )
@@ -44,7 +49,8 @@ class WirelessLinkTable(TenancyColumnsMixin, NetBoxTable):
         model = WirelessLink
         model = WirelessLink
         fields = (
         fields = (
             'pk', 'id', 'status', 'device_a', 'interface_a', 'device_b', 'interface_b', 'ssid', 'tenant',
             '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', 'distance', 'description', 'auth_type', 'auth_cipher', 'auth_psk', 'tags',
+            'created', 'last_updated',
         )
         )
         default_columns = (
         default_columns = (
             'pk', 'id', 'status', 'device_a', 'interface_a', 'device_b', 'interface_b', 'ssid', 'auth_type',
             'pk', 'id', 'status', 'device_a', 'interface_a', 'device_b', 'interface_b', 'ssid', 'auth_type',

+ 2 - 0
netbox/wireless/tests/test_api.py

@@ -113,6 +113,8 @@ class WirelessLinkTest(APIViewTestCases.APIViewTestCase):
     brief_fields = ['description', 'display', 'id', 'ssid', 'url']
     brief_fields = ['description', 'display', 'id', 'ssid', 'url']
     bulk_update_data = {
     bulk_update_data = {
         'status': 'planned',
         'status': 'planned',
+        'distance': 100,
+        'distance_unit': 'm',
     }
     }
 
 
     @classmethod
     @classmethod

+ 14 - 0
netbox/wireless/tests/test_filtersets.py

@@ -260,6 +260,8 @@ class WirelessLinkTestCase(TestCase, ChangeLoggedFilterSetTests):
             auth_cipher=WirelessAuthCipherChoices.CIPHER_AUTO,
             auth_cipher=WirelessAuthCipherChoices.CIPHER_AUTO,
             auth_psk='PSK1',
             auth_psk='PSK1',
             tenant=tenants[0],
             tenant=tenants[0],
+            distance=10,
+            distance_unit=WirelessLinkDistanceUnitChoices.UNIT_FOOT,
             description='foobar1'
             description='foobar1'
         ).save()
         ).save()
         WirelessLink(
         WirelessLink(
@@ -271,6 +273,8 @@ class WirelessLinkTestCase(TestCase, ChangeLoggedFilterSetTests):
             auth_cipher=WirelessAuthCipherChoices.CIPHER_TKIP,
             auth_cipher=WirelessAuthCipherChoices.CIPHER_TKIP,
             auth_psk='PSK2',
             auth_psk='PSK2',
             tenant=tenants[1],
             tenant=tenants[1],
+            distance=20,
+            distance_unit=WirelessLinkDistanceUnitChoices.UNIT_METER,
             description='foobar2'
             description='foobar2'
         ).save()
         ).save()
         WirelessLink(
         WirelessLink(
@@ -281,6 +285,8 @@ class WirelessLinkTestCase(TestCase, ChangeLoggedFilterSetTests):
             auth_type=WirelessAuthTypeChoices.TYPE_WPA_PERSONAL,
             auth_type=WirelessAuthTypeChoices.TYPE_WPA_PERSONAL,
             auth_cipher=WirelessAuthCipherChoices.CIPHER_AES,
             auth_cipher=WirelessAuthCipherChoices.CIPHER_AES,
             auth_psk='PSK3',
             auth_psk='PSK3',
+            distance=30,
+            distance_unit=WirelessLinkDistanceUnitChoices.UNIT_METER,
             tenant=tenants[2],
             tenant=tenants[2],
         ).save()
         ).save()
         WirelessLink(
         WirelessLink(
@@ -313,6 +319,14 @@ class WirelessLinkTestCase(TestCase, ChangeLoggedFilterSetTests):
         params = {'auth_psk': ['PSK1', 'PSK2']}
         params = {'auth_psk': ['PSK1', 'PSK2']}
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
 
 
+    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': WirelessLinkDistanceUnitChoices.UNIT_FOOT}
+        self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
+
     def test_description(self):
     def test_description(self):
         params = {'description': ['foobar1', 'foobar2']}
         params = {'description': ['foobar1', 'foobar2']}
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)

+ 4 - 0
netbox/wireless/tests/test_views.py

@@ -160,6 +160,8 @@ class WirelessLinkTestCase(ViewTestCases.PrimaryObjectViewTestCase):
             'interface_a': interfaces[6].pk,
             'interface_a': interfaces[6].pk,
             'interface_b': interfaces[7].pk,
             'interface_b': interfaces[7].pk,
             'status': LinkStatusChoices.STATUS_PLANNED,
             'status': LinkStatusChoices.STATUS_PLANNED,
+            'distance': 100,
+            'distance_unit': WirelessLinkDistanceUnitChoices.UNIT_FOOT,
             'tenant': tenants[1].pk,
             'tenant': tenants[1].pk,
             'tags': [t.pk for t in tags],
             'tags': [t.pk for t in tags],
         }
         }
@@ -180,4 +182,6 @@ class WirelessLinkTestCase(ViewTestCases.PrimaryObjectViewTestCase):
 
 
         cls.bulk_edit_data = {
         cls.bulk_edit_data = {
             'status': LinkStatusChoices.STATUS_PLANNED,
             'status': LinkStatusChoices.STATUS_PLANNED,
+            'distance': 50,
+            'distance_unit': WirelessLinkDistanceUnitChoices.UNIT_METER,
         }
         }