diff --git a/docs/models/wireless/wirelesslan.md b/docs/models/wireless/wirelesslan.md
index 5bb3dbd65..0f50fa75f 100644
--- a/docs/models/wireless/wirelesslan.md
+++ b/docs/models/wireless/wirelesslan.md
@@ -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).
+### 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
Each wireless LAN can optionally be mapped to a [VLAN](../ipam/vlan.md), to model a bridge between wired and wireless segments.
diff --git a/netbox/templates/wireless/wirelesslan.html b/netbox/templates/wireless/wirelesslan.html
index 19e8b930d..ad76f9c07 100644
--- a/netbox/templates/wireless/wirelesslan.html
+++ b/netbox/templates/wireless/wirelesslan.html
@@ -18,6 +18,10 @@
Group |
{{ object.group|linkify|placeholder }} |
+
+ Status |
+ {% badge object.get_status_display bg_color=object.get_status_color %} |
+
Description |
{{ object.description|placeholder }} |
diff --git a/netbox/wireless/api/serializers.py b/netbox/wireless/api/serializers.py
index 109c3a341..cc2c8701c 100644
--- a/netbox/wireless/api/serializers.py
+++ b/netbox/wireless/api/serializers.py
@@ -33,6 +33,7 @@ class WirelessLANGroupSerializer(NestedGroupModelSerializer):
class WirelessLANSerializer(NetBoxModelSerializer):
url = serializers.HyperlinkedIdentityField(view_name='wireless-api:wirelesslan-detail')
group = NestedWirelessLANGroupSerializer(required=False, allow_null=True)
+ status = ChoiceField(choices=WirelessLANStatusChoices, required=False, allow_blank=True)
vlan = NestedVLANSerializer(required=False, allow_null=True)
tenant = NestedTenantSerializer(required=False, allow_null=True)
auth_type = ChoiceField(choices=WirelessAuthTypeChoices, required=False, allow_blank=True)
@@ -41,8 +42,8 @@ class WirelessLANSerializer(NetBoxModelSerializer):
class Meta:
model = WirelessLAN
fields = [
- 'id', 'url', 'display', 'ssid', 'description', 'group', 'vlan', 'tenant', 'auth_type', 'auth_cipher',
- 'auth_psk', 'description', 'comments', 'tags', 'custom_fields', 'created', 'last_updated',
+ 'id', 'url', 'display', 'ssid', 'description', 'group', 'status', 'vlan', 'tenant', 'auth_type',
+ 'auth_cipher', 'auth_psk', 'description', 'comments', 'tags', 'custom_fields', 'created', 'last_updated',
]
diff --git a/netbox/wireless/choices.py b/netbox/wireless/choices.py
index 135fa1b0c..b1f283620 100644
--- a/netbox/wireless/choices.py
+++ b/netbox/wireless/choices.py
@@ -1,3 +1,5 @@
+from django.utils.translation import gettext as _
+
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):
# 2.4 GHz
diff --git a/netbox/wireless/filtersets.py b/netbox/wireless/filtersets.py
index 60c4f935b..6ffb9cb91 100644
--- a/netbox/wireless/filtersets.py
+++ b/netbox/wireless/filtersets.py
@@ -43,6 +43,9 @@ class WirelessLANFilterSet(NetBoxModelFilterSet, TenancyFilterSet):
lookup_expr='in',
to_field_name='slug'
)
+ status = django_filters.MultipleChoiceFilter(
+ choices=WirelessLANStatusChoices
+ )
vlan_id = django_filters.ModelMultipleChoiceFilter(
queryset=VLAN.objects.all()
)
diff --git a/netbox/wireless/forms/bulk_edit.py b/netbox/wireless/forms/bulk_edit.py
index 543e7e0b3..7544327a5 100644
--- a/netbox/wireless/forms/bulk_edit.py
+++ b/netbox/wireless/forms/bulk_edit.py
@@ -34,6 +34,10 @@ class WirelessLANGroupBulkEditForm(NetBoxModelBulkEditForm):
class WirelessLANBulkEditForm(NetBoxModelBulkEditForm):
+ status = forms.ChoiceField(
+ choices=add_blank_choice(WirelessLANStatusChoices),
+ required=False
+ )
group = DynamicModelChoiceField(
queryset=WirelessLANGroup.objects.all(),
required=False
@@ -75,7 +79,7 @@ class WirelessLANBulkEditForm(NetBoxModelBulkEditForm):
model = WirelessLAN
fieldsets = (
- (None, ('group', 'ssid', 'vlan', 'tenant', 'description')),
+ (None, ('group', 'ssid', 'status', 'vlan', 'tenant', 'description')),
('Authentication', ('auth_type', 'auth_cipher', 'auth_psk')),
)
nullable_fields = (
diff --git a/netbox/wireless/forms/bulk_import.py b/netbox/wireless/forms/bulk_import.py
index 00078c8eb..4d96f60ad 100644
--- a/netbox/wireless/forms/bulk_import.py
+++ b/netbox/wireless/forms/bulk_import.py
@@ -35,6 +35,10 @@ class WirelessLANCSVForm(NetBoxModelCSVForm):
to_field_name='name',
help_text='Assigned group'
)
+ status = CSVChoiceField(
+ choices=WirelessLANStatusChoices,
+ help_text='Operational status'
+ )
vlan = CSVModelChoiceField(
queryset=VLAN.objects.all(),
required=False,
@@ -61,8 +65,8 @@ class WirelessLANCSVForm(NetBoxModelCSVForm):
class Meta:
model = WirelessLAN
fields = (
- 'ssid', 'group', 'vlan', 'tenant', 'auth_type', 'auth_cipher', 'auth_psk', 'description', 'comments',
- 'tags',
+ 'ssid', 'group', 'status', 'vlan', 'tenant', 'auth_type', 'auth_cipher', 'auth_psk', 'description',
+ 'comments', 'tags',
)
diff --git a/netbox/wireless/forms/filtersets.py b/netbox/wireless/forms/filtersets.py
index d7a6aac6e..c3e63687d 100644
--- a/netbox/wireless/forms/filtersets.py
+++ b/netbox/wireless/forms/filtersets.py
@@ -29,7 +29,7 @@ class WirelessLANFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
model = WirelessLAN
fieldsets = (
(None, ('q', 'filter', 'tag')),
- ('Attributes', ('ssid', 'group_id',)),
+ ('Attributes', ('ssid', 'group_id', 'status')),
('Tenant', ('tenant_group_id', 'tenant_id')),
('Authentication', ('auth_type', 'auth_cipher', 'auth_psk')),
)
@@ -43,6 +43,11 @@ class WirelessLANFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
null_option='None',
label=_('Group')
)
+ status = forms.ChoiceField(
+ required=False,
+ choices=add_blank_choice(WirelessLANStatusChoices),
+ widget=StaticSelect()
+ )
auth_type = forms.ChoiceField(
required=False,
choices=add_blank_choice(WirelessAuthTypeChoices),
diff --git a/netbox/wireless/forms/model_forms.py b/netbox/wireless/forms/model_forms.py
index d57c74575..e59c36696 100644
--- a/netbox/wireless/forms/model_forms.py
+++ b/netbox/wireless/forms/model_forms.py
@@ -37,7 +37,6 @@ class WirelessLANForm(TenancyForm, NetBoxModelForm):
queryset=WirelessLANGroup.objects.all(),
required=False
)
-
region = DynamicModelChoiceField(
queryset=Region.objects.all(),
required=False,
@@ -85,7 +84,7 @@ class WirelessLANForm(TenancyForm, NetBoxModelForm):
comments = CommentField()
fieldsets = (
- ('Wireless LAN', ('ssid', 'group', 'description', 'tags')),
+ ('Wireless LAN', ('ssid', 'group', 'status', 'description', 'tags')),
('VLAN', ('region', 'site_group', 'site', 'vlan_group', 'vlan',)),
('Tenancy', ('tenant_group', 'tenant')),
('Authentication', ('auth_type', 'auth_cipher', 'auth_psk')),
@@ -94,10 +93,11 @@ class WirelessLANForm(TenancyForm, NetBoxModelForm):
class Meta:
model = WirelessLAN
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',
]
widgets = {
+ 'status': StaticSelect,
'auth_type': StaticSelect,
'auth_cipher': StaticSelect,
}
diff --git a/netbox/wireless/migrations/0008_wirelesslan_status.py b/netbox/wireless/migrations/0008_wirelesslan_status.py
new file mode 100644
index 000000000..e7832aba2
--- /dev/null
+++ b/netbox/wireless/migrations/0008_wirelesslan_status.py
@@ -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),
+ ),
+ ]
diff --git a/netbox/wireless/models.py b/netbox/wireless/models.py
index 96764b53c..5858e641c 100644
--- a/netbox/wireless/models.py
+++ b/netbox/wireless/models.py
@@ -84,6 +84,11 @@ class WirelessLAN(WirelessAuthenticationBase, PrimaryModel):
blank=True,
null=True
)
+ status = models.CharField(
+ max_length=50,
+ choices=WirelessLANStatusChoices,
+ default=WirelessLANStatusChoices.STATUS_ACTIVE
+ )
vlan = models.ForeignKey(
to='ipam.VLAN',
on_delete=models.PROTECT,
@@ -111,6 +116,9 @@ class WirelessLAN(WirelessAuthenticationBase, PrimaryModel):
def get_absolute_url(self):
return reverse('wireless:wirelesslan', args=[self.pk])
+ def get_status_color(self):
+ return WirelessLANStatusChoices.colors.get(self.status)
+
def get_wireless_interface_types():
# Wrap choices in a callable to avoid generating dummy migrations
diff --git a/netbox/wireless/tables/wirelesslan.py b/netbox/wireless/tables/wirelesslan.py
index 4aa5cc1fd..5d17465f0 100644
--- a/netbox/wireless/tables/wirelesslan.py
+++ b/netbox/wireless/tables/wirelesslan.py
@@ -42,6 +42,7 @@ class WirelessLANTable(TenancyColumnsMixin, NetBoxTable):
group = tables.Column(
linkify=True
)
+ status = columns.ChoiceFieldColumn()
interface_count = tables.Column(
verbose_name='Interfaces'
)
@@ -53,10 +54,10 @@ class WirelessLANTable(TenancyColumnsMixin, NetBoxTable):
class Meta(NetBoxTable.Meta):
model = WirelessLAN
fields = (
- 'pk', 'ssid', 'group', 'tenant', 'tenant_group', 'vlan', 'interface_count', 'auth_type', 'auth_cipher',
- 'auth_psk', 'description', 'comments', 'tags', 'created', 'last_updated',
+ 'pk', 'ssid', 'group', 'status', 'tenant', 'tenant_group', 'vlan', 'interface_count', 'auth_type',
+ '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):
diff --git a/netbox/wireless/tests/test_api.py b/netbox/wireless/tests/test_api.py
index 9ef552eb7..cfc17c660 100644
--- a/netbox/wireless/tests/test_api.py
+++ b/netbox/wireless/tests/test_api.py
@@ -68,9 +68,9 @@ class WirelessLANTest(APIViewTestCases.APIViewTestCase):
group.save()
wireless_lans = (
- WirelessLAN(ssid='WLAN1'),
- WirelessLAN(ssid='WLAN2'),
- WirelessLAN(ssid='WLAN3'),
+ WirelessLAN(ssid='WLAN1', status=WirelessLANStatusChoices.STATUS_ACTIVE),
+ WirelessLAN(ssid='WLAN2', status=WirelessLANStatusChoices.STATUS_ACTIVE),
+ WirelessLAN(ssid='WLAN3', status=WirelessLANStatusChoices.STATUS_ACTIVE),
)
WirelessLAN.objects.bulk_create(wireless_lans)
@@ -78,23 +78,27 @@ class WirelessLANTest(APIViewTestCases.APIViewTestCase):
{
'ssid': 'WLAN4',
'group': groups[0].pk,
+ 'status': WirelessLANStatusChoices.STATUS_DISABLED,
'tenant': tenants[0].pk,
'auth_type': WirelessAuthTypeChoices.TYPE_OPEN,
},
{
'ssid': 'WLAN5',
'group': groups[1].pk,
+ 'status': WirelessLANStatusChoices.STATUS_DISABLED,
'tenant': tenants[0].pk,
'auth_type': WirelessAuthTypeChoices.TYPE_WPA_PERSONAL,
},
{
'ssid': 'WLAN6',
+ 'status': WirelessLANStatusChoices.STATUS_DISABLED,
'tenant': tenants[0].pk,
'auth_type': WirelessAuthTypeChoices.TYPE_WPA_ENTERPRISE,
},
]
cls.bulk_update_data = {
+ 'status': WirelessLANStatusChoices.STATUS_DEPRECATED,
'group': groups[2].pk,
'tenant': tenants[1].pk,
'description': 'New description',
diff --git a/netbox/wireless/tests/test_filtersets.py b/netbox/wireless/tests/test_filtersets.py
index ffe919c32..0629fea07 100644
--- a/netbox/wireless/tests/test_filtersets.py
+++ b/netbox/wireless/tests/test_filtersets.py
@@ -64,9 +64,18 @@ class WirelessLANTestCase(TestCase, ChangeLoggedFilterSetTests):
def setUpTestData(cls):
groups = (
- WirelessLANGroup(name='Wireless LAN Group 1', 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'),
+ WirelessLANGroup(
+ name='Wireless LAN Group 1',
+ 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:
group.save()
@@ -86,9 +95,36 @@ class WirelessLANTestCase(TestCase, ChangeLoggedFilterSetTests):
Tenant.objects.bulk_create(tenants)
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(ssid='WLAN2', group=groups[1], 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], vlan=vlans[2], tenant=tenants[2], auth_type=WirelessAuthTypeChoices.TYPE_WPA_PERSONAL, auth_cipher=WirelessAuthCipherChoices.CIPHER_AES, auth_psk='PSK3'),
+ WirelessLAN(
+ ssid='WLAN1',
+ 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)
@@ -103,6 +139,10 @@ class WirelessLANTestCase(TestCase, ChangeLoggedFilterSetTests):
params = {'group': [groups[0].slug, groups[1].slug]}
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):
vlans = VLAN.objects.all()[:2]
params = {'vlan_id': [vlans[0].pk, vlans[1].pk]}
diff --git a/netbox/wireless/tests/test_views.py b/netbox/wireless/tests/test_views.py
index 615678a62..62c3b451f 100644
--- a/netbox/wireless/tests/test_views.py
+++ b/netbox/wireless/tests/test_views.py
@@ -70,9 +70,24 @@ class WirelessLANTestCase(ViewTestCases.PrimaryObjectViewTestCase):
group.save()
wireless_lans = (
- WirelessLAN(group=groups[0], ssid='WLAN1', tenant=tenants[0]),
- WirelessLAN(group=groups[0], ssid='WLAN2', tenant=tenants[0]),
- WirelessLAN(group=groups[0], ssid='WLAN3', tenant=tenants[0]),
+ WirelessLAN(
+ group=groups[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)
@@ -81,15 +96,16 @@ class WirelessLANTestCase(ViewTestCases.PrimaryObjectViewTestCase):
cls.form_data = {
'ssid': 'WLAN2',
'group': groups[1].pk,
+ 'status': WirelessLANStatusChoices.STATUS_DISABLED,
'tenant': tenants[1].pk,
'tags': [t.pk for t in tags],
}
cls.csv_data = (
- f"group,ssid,tenant",
- f"Wireless LAN Group 2,WLAN4,{tenants[0].name}",
- f"Wireless LAN Group 2,WLAN5,{tenants[1].name}",
- f"Wireless LAN Group 2,WLAN6,{tenants[2].name}",
+ f"group,ssid,status,tenant",
+ f"Wireless LAN Group 2,WLAN4,{WirelessLANStatusChoices.STATUS_ACTIVE},{tenants[0].name}",
+ f"Wireless LAN Group 2,WLAN5,{WirelessLANStatusChoices.STATUS_DISABLED},{tenants[1].name}",
+ f"Wireless LAN Group 2,WLAN6,{WirelessLANStatusChoices.STATUS_RESERVED},{tenants[2].name}",
)
cls.csv_update_data = (
@@ -100,6 +116,7 @@ class WirelessLANTestCase(ViewTestCases.PrimaryObjectViewTestCase):
)
cls.bulk_edit_data = {
+ 'status': WirelessLANStatusChoices.STATUS_DISABLED,
'description': 'New description',
}