1
0
mirror of https://github.com/netbox-community/netbox.git synced 2024-05-10 07:54:54 +00:00

Closes #10710: Add status field to WirelessLAN

This commit is contained in:
jeremystretch
2022-11-04 13:40:39 -04:00
parent cdeb65e2fb
commit ad40d42dc4
15 changed files with 162 additions and 28 deletions

View File

@ -12,6 +12,13 @@ The service set identifier (SSID) for the wireless network.
The [wireless LAN group](./wirelesslangroup.md) to which this wireless LAN is assigned (if any). The [wireless LAN group](./wirelesslangroup.md) to which this wireless LAN is assigned (if any).
### Status
The operational status of the wireless network.
!!! tip
Additional statuses may be defined by setting `WirelessLAN.status` under the [`FIELD_CHOICES`](../../configuration/data-validation.md#field_choices) configuration parameter.
### VLAN ### VLAN
Each wireless LAN can optionally be mapped to a [VLAN](../ipam/vlan.md), to model a bridge between wired and wireless segments. Each wireless LAN can optionally be mapped to a [VLAN](../ipam/vlan.md), to model a bridge between wired and wireless segments.

View File

@ -18,6 +18,10 @@
<td>Group</td> <td>Group</td>
<td>{{ object.group|linkify|placeholder }}</td> <td>{{ object.group|linkify|placeholder }}</td>
</tr> </tr>
<tr>
<td>Status</td>
<td>{% badge object.get_status_display bg_color=object.get_status_color %}</td>
</tr>
<tr> <tr>
<th scope="row">Description</th> <th scope="row">Description</th>
<td>{{ object.description|placeholder }}</td> <td>{{ object.description|placeholder }}</td>

View File

@ -33,6 +33,7 @@ class WirelessLANGroupSerializer(NestedGroupModelSerializer):
class WirelessLANSerializer(NetBoxModelSerializer): class WirelessLANSerializer(NetBoxModelSerializer):
url = serializers.HyperlinkedIdentityField(view_name='wireless-api:wirelesslan-detail') url = serializers.HyperlinkedIdentityField(view_name='wireless-api:wirelesslan-detail')
group = NestedWirelessLANGroupSerializer(required=False, allow_null=True) group = NestedWirelessLANGroupSerializer(required=False, allow_null=True)
status = ChoiceField(choices=WirelessLANStatusChoices, required=False, allow_blank=True)
vlan = NestedVLANSerializer(required=False, allow_null=True) vlan = NestedVLANSerializer(required=False, allow_null=True)
tenant = NestedTenantSerializer(required=False, allow_null=True) tenant = NestedTenantSerializer(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)
@ -41,8 +42,8 @@ class WirelessLANSerializer(NetBoxModelSerializer):
class Meta: class Meta:
model = WirelessLAN model = WirelessLAN
fields = [ fields = [
'id', 'url', 'display', 'ssid', 'description', 'group', 'vlan', 'tenant', 'auth_type', 'auth_cipher', 'id', 'url', 'display', 'ssid', 'description', 'group', 'status', 'vlan', 'tenant', 'auth_type',
'auth_psk', 'description', 'comments', 'tags', 'custom_fields', 'created', 'last_updated', 'auth_cipher', 'auth_psk', 'description', 'comments', 'tags', 'custom_fields', 'created', 'last_updated',
] ]

View File

@ -1,3 +1,5 @@
from django.utils.translation import gettext as _
from utilities.choices import ChoiceSet from utilities.choices import ChoiceSet
@ -11,6 +13,22 @@ class WirelessRoleChoices(ChoiceSet):
) )
class WirelessLANStatusChoices(ChoiceSet):
key = 'WirelessLANS.status'
STATUS_ACTIVE = 'active'
STATUS_RESERVED = 'reserved'
STATUS_DISABLED = 'disabled'
STATUS_DEPRECATED = 'deprecated'
CHOICES = [
(STATUS_ACTIVE, _('Active'), 'green'),
(STATUS_RESERVED, _('Reserved'), 'cyan'),
(STATUS_DISABLED, _('Disabled'), 'orange'),
(STATUS_DEPRECATED, _('Deprecated'), 'red'),
]
class WirelessChannelChoices(ChoiceSet): class WirelessChannelChoices(ChoiceSet):
# 2.4 GHz # 2.4 GHz

View File

@ -43,6 +43,9 @@ class WirelessLANFilterSet(NetBoxModelFilterSet, TenancyFilterSet):
lookup_expr='in', lookup_expr='in',
to_field_name='slug' to_field_name='slug'
) )
status = django_filters.MultipleChoiceFilter(
choices=WirelessLANStatusChoices
)
vlan_id = django_filters.ModelMultipleChoiceFilter( vlan_id = django_filters.ModelMultipleChoiceFilter(
queryset=VLAN.objects.all() queryset=VLAN.objects.all()
) )

View File

@ -34,6 +34,10 @@ class WirelessLANGroupBulkEditForm(NetBoxModelBulkEditForm):
class WirelessLANBulkEditForm(NetBoxModelBulkEditForm): class WirelessLANBulkEditForm(NetBoxModelBulkEditForm):
status = forms.ChoiceField(
choices=add_blank_choice(WirelessLANStatusChoices),
required=False
)
group = DynamicModelChoiceField( group = DynamicModelChoiceField(
queryset=WirelessLANGroup.objects.all(), queryset=WirelessLANGroup.objects.all(),
required=False required=False
@ -75,7 +79,7 @@ class WirelessLANBulkEditForm(NetBoxModelBulkEditForm):
model = WirelessLAN model = WirelessLAN
fieldsets = ( fieldsets = (
(None, ('group', 'ssid', 'vlan', 'tenant', 'description')), (None, ('group', 'ssid', 'status', 'vlan', 'tenant', 'description')),
('Authentication', ('auth_type', 'auth_cipher', 'auth_psk')), ('Authentication', ('auth_type', 'auth_cipher', 'auth_psk')),
) )
nullable_fields = ( nullable_fields = (

View File

@ -35,6 +35,10 @@ class WirelessLANCSVForm(NetBoxModelCSVForm):
to_field_name='name', to_field_name='name',
help_text='Assigned group' help_text='Assigned group'
) )
status = CSVChoiceField(
choices=WirelessLANStatusChoices,
help_text='Operational status'
)
vlan = CSVModelChoiceField( vlan = CSVModelChoiceField(
queryset=VLAN.objects.all(), queryset=VLAN.objects.all(),
required=False, required=False,
@ -61,8 +65,8 @@ class WirelessLANCSVForm(NetBoxModelCSVForm):
class Meta: class Meta:
model = WirelessLAN model = WirelessLAN
fields = ( fields = (
'ssid', 'group', 'vlan', 'tenant', 'auth_type', 'auth_cipher', 'auth_psk', 'description', 'comments', 'ssid', 'group', 'status', 'vlan', 'tenant', 'auth_type', 'auth_cipher', 'auth_psk', 'description',
'tags', 'comments', 'tags',
) )

View File

@ -29,7 +29,7 @@ class WirelessLANFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
model = WirelessLAN model = WirelessLAN
fieldsets = ( fieldsets = (
(None, ('q', 'filter', 'tag')), (None, ('q', 'filter', 'tag')),
('Attributes', ('ssid', 'group_id',)), ('Attributes', ('ssid', 'group_id', 'status')),
('Tenant', ('tenant_group_id', 'tenant_id')), ('Tenant', ('tenant_group_id', 'tenant_id')),
('Authentication', ('auth_type', 'auth_cipher', 'auth_psk')), ('Authentication', ('auth_type', 'auth_cipher', 'auth_psk')),
) )
@ -43,6 +43,11 @@ class WirelessLANFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
null_option='None', null_option='None',
label=_('Group') label=_('Group')
) )
status = forms.ChoiceField(
required=False,
choices=add_blank_choice(WirelessLANStatusChoices),
widget=StaticSelect()
)
auth_type = forms.ChoiceField( auth_type = forms.ChoiceField(
required=False, required=False,
choices=add_blank_choice(WirelessAuthTypeChoices), choices=add_blank_choice(WirelessAuthTypeChoices),

View File

@ -37,7 +37,6 @@ class WirelessLANForm(TenancyForm, NetBoxModelForm):
queryset=WirelessLANGroup.objects.all(), queryset=WirelessLANGroup.objects.all(),
required=False required=False
) )
region = DynamicModelChoiceField( region = DynamicModelChoiceField(
queryset=Region.objects.all(), queryset=Region.objects.all(),
required=False, required=False,
@ -85,7 +84,7 @@ class WirelessLANForm(TenancyForm, NetBoxModelForm):
comments = CommentField() comments = CommentField()
fieldsets = ( fieldsets = (
('Wireless LAN', ('ssid', 'group', 'description', 'tags')), ('Wireless LAN', ('ssid', 'group', 'status', 'description', 'tags')),
('VLAN', ('region', 'site_group', 'site', 'vlan_group', 'vlan',)), ('VLAN', ('region', 'site_group', 'site', 'vlan_group', 'vlan',)),
('Tenancy', ('tenant_group', 'tenant')), ('Tenancy', ('tenant_group', 'tenant')),
('Authentication', ('auth_type', 'auth_cipher', 'auth_psk')), ('Authentication', ('auth_type', 'auth_cipher', 'auth_psk')),
@ -94,10 +93,11 @@ class WirelessLANForm(TenancyForm, NetBoxModelForm):
class Meta: class Meta:
model = WirelessLAN model = WirelessLAN
fields = [ fields = [
'ssid', 'group', 'region', 'site_group', 'site', 'vlan_group', 'vlan', 'tenant_group', 'tenant', 'ssid', 'group', 'region', 'site_group', 'site', 'status', 'vlan_group', 'vlan', 'tenant_group', 'tenant',
'auth_type', 'auth_cipher', 'auth_psk', 'description', 'comments', 'tags', 'auth_type', 'auth_cipher', 'auth_psk', 'description', 'comments', 'tags',
] ]
widgets = { widgets = {
'status': StaticSelect,
'auth_type': StaticSelect, 'auth_type': StaticSelect,
'auth_cipher': StaticSelect, 'auth_cipher': StaticSelect,
} }

View File

@ -0,0 +1,18 @@
# Generated by Django 4.1.2 on 2022-11-04 17:07
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('wireless', '0007_standardize_description_comments'),
]
operations = [
migrations.AddField(
model_name='wirelesslan',
name='status',
field=models.CharField(default='active', max_length=50),
),
]

View File

@ -84,6 +84,11 @@ class WirelessLAN(WirelessAuthenticationBase, PrimaryModel):
blank=True, blank=True,
null=True null=True
) )
status = models.CharField(
max_length=50,
choices=WirelessLANStatusChoices,
default=WirelessLANStatusChoices.STATUS_ACTIVE
)
vlan = models.ForeignKey( vlan = models.ForeignKey(
to='ipam.VLAN', to='ipam.VLAN',
on_delete=models.PROTECT, on_delete=models.PROTECT,
@ -111,6 +116,9 @@ class WirelessLAN(WirelessAuthenticationBase, PrimaryModel):
def get_absolute_url(self): def get_absolute_url(self):
return reverse('wireless:wirelesslan', args=[self.pk]) return reverse('wireless:wirelesslan', args=[self.pk])
def get_status_color(self):
return WirelessLANStatusChoices.colors.get(self.status)
def get_wireless_interface_types(): def get_wireless_interface_types():
# Wrap choices in a callable to avoid generating dummy migrations # Wrap choices in a callable to avoid generating dummy migrations

View File

@ -42,6 +42,7 @@ class WirelessLANTable(TenancyColumnsMixin, NetBoxTable):
group = tables.Column( group = tables.Column(
linkify=True linkify=True
) )
status = columns.ChoiceFieldColumn()
interface_count = tables.Column( interface_count = tables.Column(
verbose_name='Interfaces' verbose_name='Interfaces'
) )
@ -53,10 +54,10 @@ class WirelessLANTable(TenancyColumnsMixin, NetBoxTable):
class Meta(NetBoxTable.Meta): class Meta(NetBoxTable.Meta):
model = WirelessLAN model = WirelessLAN
fields = ( fields = (
'pk', 'ssid', 'group', 'tenant', 'tenant_group', 'vlan', 'interface_count', 'auth_type', 'auth_cipher', 'pk', 'ssid', 'group', 'status', 'tenant', 'tenant_group', 'vlan', 'interface_count', 'auth_type',
'auth_psk', 'description', 'comments', 'tags', 'created', 'last_updated', 'auth_cipher', 'auth_psk', 'description', 'comments', 'tags', 'created', 'last_updated',
) )
default_columns = ('pk', 'ssid', 'group', 'description', 'vlan', 'auth_type', 'interface_count') default_columns = ('pk', 'ssid', 'group', 'status', 'description', 'vlan', 'auth_type', 'interface_count')
class WirelessLANInterfacesTable(NetBoxTable): class WirelessLANInterfacesTable(NetBoxTable):

View File

@ -68,9 +68,9 @@ class WirelessLANTest(APIViewTestCases.APIViewTestCase):
group.save() group.save()
wireless_lans = ( wireless_lans = (
WirelessLAN(ssid='WLAN1'), WirelessLAN(ssid='WLAN1', status=WirelessLANStatusChoices.STATUS_ACTIVE),
WirelessLAN(ssid='WLAN2'), WirelessLAN(ssid='WLAN2', status=WirelessLANStatusChoices.STATUS_ACTIVE),
WirelessLAN(ssid='WLAN3'), WirelessLAN(ssid='WLAN3', status=WirelessLANStatusChoices.STATUS_ACTIVE),
) )
WirelessLAN.objects.bulk_create(wireless_lans) WirelessLAN.objects.bulk_create(wireless_lans)
@ -78,23 +78,27 @@ class WirelessLANTest(APIViewTestCases.APIViewTestCase):
{ {
'ssid': 'WLAN4', 'ssid': 'WLAN4',
'group': groups[0].pk, 'group': groups[0].pk,
'status': WirelessLANStatusChoices.STATUS_DISABLED,
'tenant': tenants[0].pk, 'tenant': tenants[0].pk,
'auth_type': WirelessAuthTypeChoices.TYPE_OPEN, 'auth_type': WirelessAuthTypeChoices.TYPE_OPEN,
}, },
{ {
'ssid': 'WLAN5', 'ssid': 'WLAN5',
'group': groups[1].pk, 'group': groups[1].pk,
'status': WirelessLANStatusChoices.STATUS_DISABLED,
'tenant': tenants[0].pk, 'tenant': tenants[0].pk,
'auth_type': WirelessAuthTypeChoices.TYPE_WPA_PERSONAL, 'auth_type': WirelessAuthTypeChoices.TYPE_WPA_PERSONAL,
}, },
{ {
'ssid': 'WLAN6', 'ssid': 'WLAN6',
'status': WirelessLANStatusChoices.STATUS_DISABLED,
'tenant': tenants[0].pk, 'tenant': tenants[0].pk,
'auth_type': WirelessAuthTypeChoices.TYPE_WPA_ENTERPRISE, 'auth_type': WirelessAuthTypeChoices.TYPE_WPA_ENTERPRISE,
}, },
] ]
cls.bulk_update_data = { cls.bulk_update_data = {
'status': WirelessLANStatusChoices.STATUS_DEPRECATED,
'group': groups[2].pk, 'group': groups[2].pk,
'tenant': tenants[1].pk, 'tenant': tenants[1].pk,
'description': 'New description', 'description': 'New description',

View File

@ -64,9 +64,18 @@ class WirelessLANTestCase(TestCase, ChangeLoggedFilterSetTests):
def setUpTestData(cls): def setUpTestData(cls):
groups = ( groups = (
WirelessLANGroup(name='Wireless LAN Group 1', slug='wireless-lan-group-1'), WirelessLANGroup(
WirelessLANGroup(name='Wireless LAN Group 2', slug='wireless-lan-group-2'), name='Wireless LAN Group 1',
WirelessLANGroup(name='Wireless LAN Group 3', slug='wireless-lan-group-3'), slug='wireless-lan-group-1'
),
WirelessLANGroup(
name='Wireless LAN Group 2',
slug='wireless-lan-group-2'
),
WirelessLANGroup(
name='Wireless LAN Group 3',
slug='wireless-lan-group-3'
),
) )
for group in groups: for group in groups:
group.save() group.save()
@ -86,9 +95,36 @@ class WirelessLANTestCase(TestCase, ChangeLoggedFilterSetTests):
Tenant.objects.bulk_create(tenants) Tenant.objects.bulk_create(tenants)
wireless_lans = ( wireless_lans = (
WirelessLAN(ssid='WLAN1', group=groups[0], vlan=vlans[0], tenant=tenants[0], auth_type=WirelessAuthTypeChoices.TYPE_OPEN, auth_cipher=WirelessAuthCipherChoices.CIPHER_AUTO, auth_psk='PSK1'), WirelessLAN(
WirelessLAN(ssid='WLAN2', group=groups[1], vlan=vlans[1], tenant=tenants[1], auth_type=WirelessAuthTypeChoices.TYPE_WEP, auth_cipher=WirelessAuthCipherChoices.CIPHER_TKIP, auth_psk='PSK2'), ssid='WLAN1',
WirelessLAN(ssid='WLAN3', group=groups[2], vlan=vlans[2], tenant=tenants[2], auth_type=WirelessAuthTypeChoices.TYPE_WPA_PERSONAL, auth_cipher=WirelessAuthCipherChoices.CIPHER_AES, auth_psk='PSK3'), group=groups[0],
status=WirelessLANStatusChoices.STATUS_ACTIVE,
vlan=vlans[0],
tenant=tenants[0],
auth_type=WirelessAuthTypeChoices.TYPE_OPEN,
auth_cipher=WirelessAuthCipherChoices.CIPHER_AUTO,
auth_psk='PSK1'
),
WirelessLAN(
ssid='WLAN2',
group=groups[1],
status=WirelessLANStatusChoices.STATUS_DISABLED,
vlan=vlans[1],
tenant=tenants[1],
auth_type=WirelessAuthTypeChoices.TYPE_WEP,
auth_cipher=WirelessAuthCipherChoices.CIPHER_TKIP,
auth_psk='PSK2'
),
WirelessLAN(
ssid='WLAN3',
group=groups[2],
status=WirelessLANStatusChoices.STATUS_RESERVED,
vlan=vlans[2],
tenant=tenants[2],
auth_type=WirelessAuthTypeChoices.TYPE_WPA_PERSONAL,
auth_cipher=WirelessAuthCipherChoices.CIPHER_AES,
auth_psk='PSK3'
),
) )
WirelessLAN.objects.bulk_create(wireless_lans) WirelessLAN.objects.bulk_create(wireless_lans)
@ -103,6 +139,10 @@ class WirelessLANTestCase(TestCase, ChangeLoggedFilterSetTests):
params = {'group': [groups[0].slug, groups[1].slug]} params = {'group': [groups[0].slug, groups[1].slug]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
def test_status(self):
params = {'status': [WirelessLANStatusChoices.STATUS_ACTIVE, WirelessLANStatusChoices.STATUS_DISABLED]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
def test_vlan(self): def test_vlan(self):
vlans = VLAN.objects.all()[:2] vlans = VLAN.objects.all()[:2]
params = {'vlan_id': [vlans[0].pk, vlans[1].pk]} params = {'vlan_id': [vlans[0].pk, vlans[1].pk]}

View File

@ -70,9 +70,24 @@ class WirelessLANTestCase(ViewTestCases.PrimaryObjectViewTestCase):
group.save() group.save()
wireless_lans = ( wireless_lans = (
WirelessLAN(group=groups[0], ssid='WLAN1', tenant=tenants[0]), WirelessLAN(
WirelessLAN(group=groups[0], ssid='WLAN2', tenant=tenants[0]), group=groups[0],
WirelessLAN(group=groups[0], ssid='WLAN3', tenant=tenants[0]), ssid='WLAN1',
status=WirelessLANStatusChoices.STATUS_ACTIVE,
tenant=tenants[0]
),
WirelessLAN(
group=groups[0],
ssid='WLAN2',
status=WirelessLANStatusChoices.STATUS_ACTIVE,
tenant=tenants[0]
),
WirelessLAN(
group=groups[0],
ssid='WLAN3',
status=WirelessLANStatusChoices.STATUS_ACTIVE,
tenant=tenants[0]
),
) )
WirelessLAN.objects.bulk_create(wireless_lans) WirelessLAN.objects.bulk_create(wireless_lans)
@ -81,15 +96,16 @@ class WirelessLANTestCase(ViewTestCases.PrimaryObjectViewTestCase):
cls.form_data = { cls.form_data = {
'ssid': 'WLAN2', 'ssid': 'WLAN2',
'group': groups[1].pk, 'group': groups[1].pk,
'status': WirelessLANStatusChoices.STATUS_DISABLED,
'tenant': tenants[1].pk, 'tenant': tenants[1].pk,
'tags': [t.pk for t in tags], 'tags': [t.pk for t in tags],
} }
cls.csv_data = ( cls.csv_data = (
f"group,ssid,tenant", f"group,ssid,status,tenant",
f"Wireless LAN Group 2,WLAN4,{tenants[0].name}", f"Wireless LAN Group 2,WLAN4,{WirelessLANStatusChoices.STATUS_ACTIVE},{tenants[0].name}",
f"Wireless LAN Group 2,WLAN5,{tenants[1].name}", f"Wireless LAN Group 2,WLAN5,{WirelessLANStatusChoices.STATUS_DISABLED},{tenants[1].name}",
f"Wireless LAN Group 2,WLAN6,{tenants[2].name}", f"Wireless LAN Group 2,WLAN6,{WirelessLANStatusChoices.STATUS_RESERVED},{tenants[2].name}",
) )
cls.csv_update_data = ( cls.csv_update_data = (
@ -100,6 +116,7 @@ class WirelessLANTestCase(ViewTestCases.PrimaryObjectViewTestCase):
) )
cls.bulk_edit_data = { cls.bulk_edit_data = {
'status': WirelessLANStatusChoices.STATUS_DISABLED,
'description': 'New description', 'description': 'New description',
} }