Closes #7606: Model transmit power for interfaces

This commit is contained in:
jeremystretch 2021-10-28 09:31:45 -04:00
parent 33c0c8cf6a
commit a173083e5b
15 changed files with 51 additions and 17 deletions

View File

@ -6,7 +6,7 @@
### Breaking Changes ### Breaking Changes
* The `tenant` and `tenant_id` filters for the Cable model now filter on the tenant assigned directly to each cable, rather than on the parent object of either termination. * The `tenant` and `tenant_id` filters for the Cable model now filter on the tenant assigned directly to each cable, rather than on the parent object of either termination.
* The `cable_peer` and `cable_peer_type` attributes of the interface model has been renamed to `link_peer` and `link_peer_type`, respectively, to accommodate wireless links. * The `cable_peer` and `cable_peer_type` attributes of cable termination models have been renamed to `link_peer` and `link_peer_type`, respectively, to accommodate wireless links between interfaces.
### New Features ### New Features
@ -68,6 +68,7 @@ Multiple interfaces can be bridged to a single virtual interface to effect a bri
* [#6874](https://github.com/netbox-community/netbox/issues/6874) - Add tenant assignment for locations * [#6874](https://github.com/netbox-community/netbox/issues/6874) - Add tenant assignment for locations
* [#7354](https://github.com/netbox-community/netbox/issues/7354) - Relax uniqueness constraints on region, site group, and location names * [#7354](https://github.com/netbox-community/netbox/issues/7354) - Relax uniqueness constraints on region, site group, and location names
* [#7530](https://github.com/netbox-community/netbox/issues/7530) - Move device type component lists to separate views * [#7530](https://github.com/netbox-community/netbox/issues/7530) - Move device type component lists to separate views
* [#7606](https://github.com/netbox-community/netbox/issues/7606) - Model transmit power for interfaces
### Other Changes ### Other Changes
@ -113,6 +114,7 @@ Multiple interfaces can be bridged to a single virtual interface to effect a bri
* Added `rf_channel` field * Added `rf_channel` field
* Added `rf_channel_frequency` field * Added `rf_channel_frequency` field
* Added `rf_chanel_width` field * Added `rf_chanel_width` field
* Added `tx_power` field
* Added `wwn` field * Added `wwn` field
* `cable_peer` has been renamed to `link_peer` * `cable_peer` has been renamed to `link_peer`
* `cable_peer_type` has been renamed to `link_peer_type` * `cable_peer_type` has been renamed to `link_peer_type`

View File

@ -628,7 +628,7 @@ class InterfaceSerializer(PrimaryModelSerializer, LinkTerminationSerializer, Con
fields = [ fields = [
'id', 'url', 'display', 'device', 'name', 'label', 'type', 'enabled', 'parent', 'bridge', 'lag', 'mtu', 'id', 'url', 'display', 'device', 'name', 'label', 'type', 'enabled', 'parent', 'bridge', 'lag', 'mtu',
'mac_address', 'wwn', 'mgmt_only', 'description', 'mode', 'rf_role', 'rf_channel', 'rf_channel_frequency', 'mac_address', 'wwn', 'mgmt_only', 'description', 'mode', 'rf_role', 'rf_channel', 'rf_channel_frequency',
'rf_channel_width', 'untagged_vlan', 'tagged_vlans', 'mark_connected', 'cable', 'wireless_link', 'rf_channel_width', 'tx_power', 'untagged_vlan', 'tagged_vlans', 'mark_connected', 'cable', 'wireless_link',
'link_peer', 'link_peer_type', 'connected_endpoint', 'connected_endpoint_type', 'link_peer', 'link_peer_type', 'connected_endpoint', 'connected_endpoint_type',
'connected_endpoint_reachable', 'tags', 'custom_fields', 'created', 'last_updated', 'count_ipaddresses', 'connected_endpoint_reachable', 'tags', 'custom_fields', 'created', 'last_updated', 'count_ipaddresses',
'_occupied', '_occupied',

View File

@ -1011,7 +1011,7 @@ class InterfaceFilterSet(PrimaryModelFilterSet, DeviceComponentFilterSet, CableT
model = Interface model = Interface
fields = [ fields = [
'id', 'name', 'label', 'type', 'enabled', 'mtu', 'mgmt_only', 'mode', 'rf_role', 'rf_channel', 'id', 'name', 'label', 'type', 'enabled', 'mtu', 'mgmt_only', 'mode', 'rf_role', 'rf_channel',
'rf_channel_frequency', 'rf_channel_width', 'description', 'rf_channel_frequency', 'rf_channel_width', 'tx_power', 'description',
] ]
def filter_device(self, queryset, name, value): def filter_device(self, queryset, name, value):

View File

@ -940,7 +940,7 @@ class PowerOutletBulkEditForm(
class InterfaceBulkEditForm( class InterfaceBulkEditForm(
form_from_model(Interface, [ form_from_model(Interface, [
'label', 'type', 'parent', 'bridge', 'lag', 'mac_address', 'wwn', 'mtu', 'mgmt_only', 'mark_connected', 'label', 'type', 'parent', 'bridge', 'lag', 'mac_address', 'wwn', 'mtu', 'mgmt_only', 'mark_connected',
'description', 'mode', 'rf_role', 'rf_channel', 'rf_channel_frequency', 'rf_channel_width', 'description', 'mode', 'rf_role', 'rf_channel', 'rf_channel_frequency', 'rf_channel_width', 'tx_power',
]), ]),
BootstrapMixin, BootstrapMixin,
AddRemoveTagsForm, AddRemoveTagsForm,
@ -996,7 +996,7 @@ class InterfaceBulkEditForm(
class Meta: class Meta:
nullable_fields = [ nullable_fields = [
'label', 'parent', 'bridge', 'lag', 'mac_address', 'wwn', 'mtu', 'description', 'mode', 'rf_channel', 'label', 'parent', 'bridge', 'lag', 'mac_address', 'wwn', 'mtu', 'description', 'mode', 'rf_channel',
'rf_channel_frequency', 'rf_channel_width', 'untagged_vlan', 'tagged_vlans', 'rf_channel_frequency', 'rf_channel_width', 'tx_power', 'untagged_vlan', 'tagged_vlans',
] ]
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):

View File

@ -602,7 +602,7 @@ class InterfaceCSVForm(CustomFieldModelCSVForm):
fields = ( fields = (
'device', 'name', 'label', 'parent', 'bridge', 'lag', 'type', 'enabled', 'mark_connected', 'mac_address', 'device', 'name', 'label', 'parent', 'bridge', 'lag', 'type', 'enabled', 'mark_connected', 'mac_address',
'wwn', 'mtu', 'mgmt_only', 'description', 'mode', 'rf_role', 'rf_channel', 'rf_channel_frequency', 'wwn', 'mtu', 'mgmt_only', 'description', 'mode', 'rf_role', 'rf_channel', 'rf_channel_frequency',
'rf_channel_width', 'rf_channel_width', 'tx_power',
) )
def clean_enabled(self): def clean_enabled(self):

View File

@ -957,7 +957,7 @@ class InterfaceFilterForm(DeviceComponentFilterForm):
field_groups = [ field_groups = [
['q', 'tag'], ['q', 'tag'],
['name', 'label', 'kind', 'type', 'enabled', 'mgmt_only', 'mac_address', 'wwn'], ['name', 'label', 'kind', 'type', 'enabled', 'mgmt_only', 'mac_address', 'wwn'],
['rf_role', 'rf_channel', 'rf_channel_width'], ['rf_role', 'rf_channel', 'rf_channel_width', 'tx_power'],
['region_id', 'site_group_id', 'site_id', 'location_id', 'device_id'], ['region_id', 'site_group_id', 'site_id', 'location_id', 'device_id'],
] ]
kind = forms.MultipleChoiceField( kind = forms.MultipleChoiceField(
@ -1010,6 +1010,12 @@ class InterfaceFilterForm(DeviceComponentFilterForm):
required=False, required=False,
label='Channel width (MHz)' label='Channel width (MHz)'
) )
tx_power = forms.IntegerField(
required=False,
label='Transmit power (dBm)',
min_value=0,
max_value=127
)
tag = TagFilterField(model) tag = TagFilterField(model)

View File

@ -1150,7 +1150,7 @@ class InterfaceForm(BootstrapMixin, InterfaceCommonForm, CustomFieldModelForm):
fields = [ fields = [
'device', 'name', 'label', 'type', 'enabled', 'parent', 'bridge', 'lag', 'mac_address', 'wwn', 'mtu', 'device', 'name', 'label', 'type', 'enabled', 'parent', 'bridge', 'lag', 'mac_address', 'wwn', 'mtu',
'mgmt_only', 'mark_connected', 'description', 'mode', 'rf_role', 'rf_channel', 'rf_channel_frequency', 'mgmt_only', 'mark_connected', 'description', 'mode', 'rf_role', 'rf_channel', 'rf_channel_frequency',
'rf_channel_width', 'wireless_lans', 'untagged_vlan', 'tagged_vlans', 'tags', 'rf_channel_width', 'tx_power', 'wireless_lans', 'untagged_vlan', 'tagged_vlans', 'tags',
] ]
widgets = { widgets = {
'device': forms.HiddenInput(), 'device': forms.HiddenInput(),

View File

@ -1,4 +1,5 @@
from django.db import migrations, models from django.db import migrations, models
import django.core.validators
import django.db.models.deletion import django.db.models.deletion
@ -30,6 +31,11 @@ class Migration(migrations.Migration):
name='rf_channel_width', name='rf_channel_width',
field=models.DecimalField(blank=True, decimal_places=3, max_digits=7, null=True), field=models.DecimalField(blank=True, decimal_places=3, max_digits=7, null=True),
), ),
migrations.AddField(
model_name='interface',
name='tx_power',
field=models.PositiveSmallIntegerField(blank=True, null=True, validators=[django.core.validators.MaxValueValidator(127)]),
),
migrations.AddField( migrations.AddField(
model_name='interface', model_name='interface',
name='wireless_lans', name='wireless_lans',

View File

@ -560,6 +560,12 @@ class Interface(ComponentModel, BaseInterface, LinkTermination, PathEndpoint):
null=True, null=True,
verbose_name='Channel width (MHz)' verbose_name='Channel width (MHz)'
) )
tx_power = models.PositiveSmallIntegerField(
blank=True,
null=True,
validators=(MaxValueValidator(127),),
verbose_name='Transmit power (dBm)'
)
wireless_link = models.ForeignKey( wireless_link = models.ForeignKey(
to='wireless.WirelessLink', to='wireless.WirelessLink',
on_delete=models.SET_NULL, on_delete=models.SET_NULL,

View File

@ -498,9 +498,9 @@ class InterfaceTable(DeviceComponentTable, BaseInterfaceTable, PathEndpointTable
model = Interface model = Interface
fields = ( fields = (
'pk', 'name', 'device', 'label', 'enabled', 'type', 'mgmt_only', 'mtu', 'mode', 'mac_address', 'wwn', 'pk', 'name', 'device', 'label', 'enabled', 'type', 'mgmt_only', 'mtu', 'mode', 'mac_address', 'wwn',
'rf_role', 'rf_channel', 'rf_channel_frequency', 'rf_channel_width', 'description', 'mark_connected', 'rf_role', 'rf_channel', 'rf_channel_frequency', 'rf_channel_width', 'tx_power', 'description',
'cable', 'cable_color', 'wireless_link', 'wireless_lans', 'link_peer', 'connection', 'tags', 'ip_addresses', 'mark_connected', 'cable', 'cable_color', 'wireless_link', 'wireless_lans', 'link_peer', 'connection',
'untagged_vlan', 'tagged_vlans', 'tags', 'ip_addresses', 'untagged_vlan', 'tagged_vlans',
) )
default_columns = ('pk', 'name', 'device', 'label', 'enabled', 'type', 'description') default_columns = ('pk', 'name', 'device', 'label', 'enabled', 'type', 'description')
@ -533,9 +533,9 @@ class DeviceInterfaceTable(InterfaceTable):
model = Interface model = Interface
fields = ( fields = (
'pk', 'name', 'label', 'enabled', 'type', 'parent', 'bridge', 'lag', 'mgmt_only', 'mtu', 'mode', 'pk', 'name', 'label', 'enabled', 'type', 'parent', 'bridge', 'lag', 'mgmt_only', 'mtu', 'mode',
'mac_address', 'wwn', 'rf_role', 'rf_channel', 'rf_channel_width', 'description', 'mark_connected', 'cable', 'mac_address', 'wwn', 'rf_role', 'rf_channel', 'rf_channel_width', 'tx_power', 'description',
'cable_color', 'wireless_link', 'wireless_lans', 'link_peer', 'connection', 'tags', 'ip_addresses', 'mark_connected', 'cable', 'cable_color', 'wireless_link', 'wireless_lans', 'link_peer', 'connection',
'untagged_vlan', 'tagged_vlans', 'actions', 'tags', 'ip_addresses', 'untagged_vlan', 'tagged_vlans', 'actions',
) )
order_by = ('name',) order_by = ('name',)
default_columns = ( default_columns = (

View File

@ -1198,6 +1198,7 @@ class InterfaceTest(Mixins.ComponentTraceMixin, APIViewTestCases.APIViewTestCase
'name': 'Interface 4', 'name': 'Interface 4',
'type': '1000base-t', 'type': '1000base-t',
'mode': InterfaceModeChoices.MODE_TAGGED, 'mode': InterfaceModeChoices.MODE_TAGGED,
'tx_power': 10,
'tagged_vlans': [vlans[0].pk, vlans[1].pk], 'tagged_vlans': [vlans[0].pk, vlans[1].pk],
'untagged_vlan': vlans[2].pk, 'untagged_vlan': vlans[2].pk,
}, },
@ -1207,6 +1208,7 @@ class InterfaceTest(Mixins.ComponentTraceMixin, APIViewTestCases.APIViewTestCase
'type': '1000base-t', 'type': '1000base-t',
'mode': InterfaceModeChoices.MODE_TAGGED, 'mode': InterfaceModeChoices.MODE_TAGGED,
'bridge': interfaces[0].pk, 'bridge': interfaces[0].pk,
'tx_power': 10,
'tagged_vlans': [vlans[0].pk, vlans[1].pk], 'tagged_vlans': [vlans[0].pk, vlans[1].pk],
'untagged_vlan': vlans[2].pk, 'untagged_vlan': vlans[2].pk,
}, },
@ -1216,6 +1218,7 @@ class InterfaceTest(Mixins.ComponentTraceMixin, APIViewTestCases.APIViewTestCase
'type': 'virtual', 'type': 'virtual',
'mode': InterfaceModeChoices.MODE_TAGGED, 'mode': InterfaceModeChoices.MODE_TAGGED,
'parent': interfaces[1].pk, 'parent': interfaces[1].pk,
'tx_power': 10,
'tagged_vlans': [vlans[0].pk, vlans[1].pk], 'tagged_vlans': [vlans[0].pk, vlans[1].pk],
'untagged_vlan': vlans[2].pk, 'untagged_vlan': vlans[2].pk,
}, },

View File

@ -2061,9 +2061,9 @@ class InterfaceTestCase(TestCase, ChangeLoggedFilterSetTests):
Interface(device=devices[0], name='Interface 1', label='A', type=InterfaceTypeChoices.TYPE_1GE_SFP, enabled=True, mgmt_only=True, mtu=100, mode=InterfaceModeChoices.MODE_ACCESS, mac_address='00-00-00-00-00-01', description='First'), Interface(device=devices[0], name='Interface 1', label='A', type=InterfaceTypeChoices.TYPE_1GE_SFP, enabled=True, mgmt_only=True, mtu=100, mode=InterfaceModeChoices.MODE_ACCESS, mac_address='00-00-00-00-00-01', description='First'),
Interface(device=devices[1], name='Interface 2', label='B', type=InterfaceTypeChoices.TYPE_1GE_GBIC, enabled=True, mgmt_only=True, mtu=200, mode=InterfaceModeChoices.MODE_TAGGED, mac_address='00-00-00-00-00-02', description='Second'), Interface(device=devices[1], name='Interface 2', label='B', type=InterfaceTypeChoices.TYPE_1GE_GBIC, enabled=True, mgmt_only=True, mtu=200, mode=InterfaceModeChoices.MODE_TAGGED, mac_address='00-00-00-00-00-02', description='Second'),
Interface(device=devices[2], name='Interface 3', label='C', type=InterfaceTypeChoices.TYPE_1GE_FIXED, enabled=False, mgmt_only=False, mtu=300, mode=InterfaceModeChoices.MODE_TAGGED_ALL, mac_address='00-00-00-00-00-03', description='Third'), Interface(device=devices[2], name='Interface 3', label='C', type=InterfaceTypeChoices.TYPE_1GE_FIXED, enabled=False, mgmt_only=False, mtu=300, mode=InterfaceModeChoices.MODE_TAGGED_ALL, mac_address='00-00-00-00-00-03', description='Third'),
Interface(device=devices[3], name='Interface 4', label='D', type=InterfaceTypeChoices.TYPE_OTHER, enabled=True, mgmt_only=True), Interface(device=devices[3], name='Interface 4', label='D', type=InterfaceTypeChoices.TYPE_OTHER, enabled=True, mgmt_only=True, tx_power=40),
Interface(device=devices[3], name='Interface 5', label='E', type=InterfaceTypeChoices.TYPE_OTHER, enabled=True, mgmt_only=True), Interface(device=devices[3], name='Interface 5', label='E', type=InterfaceTypeChoices.TYPE_OTHER, enabled=True, mgmt_only=True, tx_power=40),
Interface(device=devices[3], name='Interface 6', label='F', type=InterfaceTypeChoices.TYPE_OTHER, enabled=False, mgmt_only=False), Interface(device=devices[3], name='Interface 6', label='F', type=InterfaceTypeChoices.TYPE_OTHER, enabled=False, mgmt_only=False, tx_power=40),
Interface(device=devices[3], name='Interface 7', type=InterfaceTypeChoices.TYPE_80211AC, rf_role=WirelessRoleChoices.ROLE_AP, rf_channel=WirelessChannelChoices.CHANNEL_24G_1, rf_channel_frequency=2412, rf_channel_width=22), Interface(device=devices[3], name='Interface 7', type=InterfaceTypeChoices.TYPE_80211AC, rf_role=WirelessRoleChoices.ROLE_AP, rf_channel=WirelessChannelChoices.CHANNEL_24G_1, rf_channel_frequency=2412, rf_channel_width=22),
Interface(device=devices[3], name='Interface 8', type=InterfaceTypeChoices.TYPE_80211AC, rf_role=WirelessRoleChoices.ROLE_STATION, rf_channel=WirelessChannelChoices.CHANNEL_5G_32, rf_channel_frequency=5160, rf_channel_width=20), Interface(device=devices[3], name='Interface 8', type=InterfaceTypeChoices.TYPE_80211AC, rf_role=WirelessRoleChoices.ROLE_STATION, rf_channel=WirelessChannelChoices.CHANNEL_5G_32, rf_channel_frequency=5160, rf_channel_width=20),
) )
@ -2224,6 +2224,10 @@ class InterfaceTestCase(TestCase, ChangeLoggedFilterSetTests):
params = {'rf_channel_width': [22, 20]} params = {'rf_channel_width': [22, 20]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
def test_tx_power(self):
params = {'tx_power': [40]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 3)
class FrontPortTestCase(TestCase, ChangeLoggedFilterSetTests): class FrontPortTestCase(TestCase, ChangeLoggedFilterSetTests):
queryset = FrontPort.objects.all() queryset = FrontPort.objects.all()

View File

@ -1608,6 +1608,7 @@ class InterfaceTestCase(ViewTestCases.DeviceComponentViewTestCase):
'mgmt_only': True, 'mgmt_only': True,
'description': 'A front port', 'description': 'A front port',
'mode': InterfaceModeChoices.MODE_TAGGED, 'mode': InterfaceModeChoices.MODE_TAGGED,
'tx_power': 10,
'untagged_vlan': vlans[0].pk, 'untagged_vlan': vlans[0].pk,
'tagged_vlans': [v.pk for v in vlans[1:4]], 'tagged_vlans': [v.pk for v in vlans[1:4]],
'tags': [t.pk for t in tags], 'tags': [t.pk for t in tags],
@ -1641,6 +1642,7 @@ class InterfaceTestCase(ViewTestCases.DeviceComponentViewTestCase):
'mgmt_only': True, 'mgmt_only': True,
'description': 'New description', 'description': 'New description',
'mode': InterfaceModeChoices.MODE_TAGGED, 'mode': InterfaceModeChoices.MODE_TAGGED,
'tx_power': 10,
'untagged_vlan': vlans[0].pk, 'untagged_vlan': vlans[0].pk,
'tagged_vlans': [v.pk for v in vlans[1:4]], 'tagged_vlans': [v.pk for v in vlans[1:4]],
} }

View File

@ -105,6 +105,10 @@
<th scope="row">WWN</th> <th scope="row">WWN</th>
<td><span class="text-monospace">{{ object.wwn|placeholder }}</span></td> <td><span class="text-monospace">{{ object.wwn|placeholder }}</span></td>
</tr> </tr>
<tr>
<th scope="row">Transmit power (dBm)</th>
<td>{{ object.tx_power|placeholder }}</td>
</tr>
<tr> <tr>
<th scope="row">802.1Q Mode</th> <th scope="row">802.1Q Mode</th>
<td>{{ object.get_mode_display|placeholder }}</td> <td>{{ object.get_mode_display|placeholder }}</td>

View File

@ -23,6 +23,7 @@
{% render_field form.mac_address %} {% render_field form.mac_address %}
{% render_field form.wwn %} {% render_field form.wwn %}
{% render_field form.mtu %} {% render_field form.mtu %}
{% render_field form.tx_power %}
{% render_field form.description %} {% render_field form.description %}
{% render_field form.tags %} {% render_field form.tags %}
{% render_field form.enabled %} {% render_field form.enabled %}