diff --git a/docs/models/wireless/wirelesslan.md b/docs/models/wireless/wirelesslan.md
index 80a3a40b0..cb478664c 100644
--- a/docs/models/wireless/wirelesslan.md
+++ b/docs/models/wireless/wirelesslan.md
@@ -1,6 +1,6 @@
# Wireless LANs
-A wireless LAN is a set of interfaces connected via a common wireless channel. Each instance must have an SSID, and may optionally be correlated to a VLAN. Wireless LANs can be arranged into hierarchical groups.
+A wireless LAN is a set of interfaces connected via a common wireless channel. Each instance must have an SSID, and may optionally be correlated to a VLAN. Wireless LANs can be arranged into hierarchical groups, and each may be associated with a particular tenant.
An interface may be attached to multiple wireless LANs, provided they are all operating on the same channel. Only wireless interfaces may be attached to wireless LANs.
diff --git a/docs/models/wireless/wirelesslink.md b/docs/models/wireless/wirelesslink.md
index 85cdbd6d9..f52dc7191 100644
--- a/docs/models/wireless/wirelesslink.md
+++ b/docs/models/wireless/wirelesslink.md
@@ -1,6 +1,6 @@
# Wireless Links
-A wireless link represents a connection between exactly two wireless interfaces. It may optionally be assigned an SSID and a description. It may also have a status assigned to it, similar to the cable model.
+A wireless link represents a connection between exactly two wireless interfaces. It may optionally be assigned an SSID and a description. It may also have a status assigned to it, similar to the cable model. Each wireless link may also be assigned to a particular tenant.
Each wireless link may have authentication attributes associated with it, including:
diff --git a/docs/release-notes/version-3.3.md b/docs/release-notes/version-3.3.md
index 8deee0370..1e18de1e6 100644
--- a/docs/release-notes/version-3.3.md
+++ b/docs/release-notes/version-3.3.md
@@ -28,6 +28,7 @@
* [#8495](https://github.com/netbox-community/netbox/issues/8495) - Enable custom field grouping
* [#8995](https://github.com/netbox-community/netbox/issues/8995) - Enable arbitrary ordering of REST API results
* [#9166](https://github.com/netbox-community/netbox/issues/9166) - Add UI visibility toggle for custom fields
+* [#9177](https://github.com/netbox-community/netbox/issues/9177) - Add tenant assignment for wireless LANs & links
* [#9536](https://github.com/netbox-community/netbox/issues/9536) - Track API token usage times
* [#9582](https://github.com/netbox-community/netbox/issues/9582) - Enable assigning config contexts based on device location
@@ -70,3 +71,7 @@
* Added `device` field
* The `site` field is now directly writable (rather than being inferred from the assigned cluster)
* The `cluster` field is now optional. A virtual machine must have a site and/or cluster assigned.
+wireless.WirelessLAN
+ * Added `tenant` field
+wireless.WirelessLink
+ * Added `tenant` field
diff --git a/netbox/templates/tenancy/tenant.html b/netbox/templates/tenancy/tenant.html
index 52c13e1aa..e8dc4b23a 100644
--- a/netbox/templates/tenancy/tenant.html
+++ b/netbox/templates/tenancy/tenant.html
@@ -61,6 +61,10 @@
-
-
-
-
-
- SSID |
- {{ object.ssid }} |
-
-
- Group |
- {{ object.group|linkify|placeholder }} |
-
-
- Description |
- {{ object.description|placeholder }} |
-
-
- VLAN |
- {{ object.vlan|linkify|placeholder }} |
-
-
-
-
- {% include 'inc/panels/tags.html' %}
- {% plugin_left_page object %}
+
+
+
+
+
+ SSID |
+ {{ object.ssid }} |
+
+
+ Group |
+ {{ object.group|linkify|placeholder }} |
+
+
+ Description |
+ {{ object.description|placeholder }} |
+
+
+ VLAN |
+ {{ object.vlan|linkify|placeholder }} |
+
+
+ Tenant |
+
+ {% if object.tenant.group %}
+ {{ object.tenant.group|linkify }} /
+ {% endif %}
+ {{ object.tenant|linkify|placeholder }}
+ |
+
+
+
-
- {% include 'wireless/inc/authentication_attrs.html' %}
- {% include 'inc/panels/custom_fields.html' %}
- {% plugin_right_page object %}
+ {% include 'inc/panels/tags.html' %}
+ {% plugin_left_page object %}
+
+
+ {% include 'wireless/inc/authentication_attrs.html' %}
+ {% include 'inc/panels/custom_fields.html' %}
+ {% plugin_right_page object %}
diff --git a/netbox/templates/wireless/wirelesslink.html b/netbox/templates/wireless/wirelesslink.html
index 4795dcdde..d1a93e40d 100644
--- a/netbox/templates/wireless/wirelesslink.html
+++ b/netbox/templates/wireless/wirelesslink.html
@@ -23,6 +23,15 @@
SSID |
{{ object.ssid|placeholder }} |
+
+ Tenant |
+
+ {% if object.tenant.group %}
+ {{ object.tenant.group|linkify }} /
+ {% endif %}
+ {{ object.tenant|linkify|placeholder }}
+ |
+
Description |
{{ object.description|placeholder }} |
diff --git a/netbox/tenancy/views.py b/netbox/tenancy/views.py
index f6f95b123..07a25b5a4 100644
--- a/netbox/tenancy/views.py
+++ b/netbox/tenancy/views.py
@@ -7,6 +7,7 @@ from ipam.models import Aggregate, IPAddress, IPRange, Prefix, VLAN, VRF, ASN
from netbox.views import generic
from utilities.utils import count_related
from virtualization.models import VirtualMachine, Cluster
+from wireless.models import WirelessLAN, WirelessLink
from . import filtersets, forms, tables
from .models import *
@@ -114,6 +115,8 @@ class TenantView(generic.ObjectView):
'cluster_count': Cluster.objects.restrict(request.user, 'view').filter(tenant=instance).count(),
'cable_count': Cable.objects.restrict(request.user, 'view').filter(tenant=instance).count(),
'asn_count': ASN.objects.restrict(request.user, 'view').filter(tenant=instance).count(),
+ 'wirelesslan_count': WirelessLAN.objects.restrict(request.user, 'view').filter(tenant=instance).count(),
+ 'wirelesslink_count': WirelessLink.objects.restrict(request.user, 'view').filter(tenant=instance).count(),
}
return {
diff --git a/netbox/wireless/api/serializers.py b/netbox/wireless/api/serializers.py
index 4a6abe94d..49d512e51 100644
--- a/netbox/wireless/api/serializers.py
+++ b/netbox/wireless/api/serializers.py
@@ -5,6 +5,7 @@ from dcim.api.serializers import NestedInterfaceSerializer
from ipam.api.serializers import NestedVLANSerializer
from netbox.api import ChoiceField
from netbox.api.serializers import NestedGroupModelSerializer, NetBoxModelSerializer
+from tenancy.api.nested_serializers import NestedTenantSerializer
from wireless.choices import *
from wireless.models import *
from .nested_serializers import *
@@ -33,14 +34,15 @@ class WirelessLANSerializer(NetBoxModelSerializer):
url = serializers.HyperlinkedIdentityField(view_name='wireless-api:wirelesslan-detail')
group = NestedWirelessLANGroupSerializer(required=False, allow_null=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)
auth_cipher = ChoiceField(choices=WirelessAuthCipherChoices, required=False, allow_blank=True)
class Meta:
model = WirelessLAN
fields = [
- 'id', 'url', 'display', 'ssid', 'description', 'group', 'vlan', 'auth_type', 'auth_cipher', 'auth_psk',
- 'description', 'tags', 'custom_fields', 'created', 'last_updated',
+ 'id', 'url', 'display', 'ssid', 'description', 'group', 'vlan', 'tenant', 'auth_type', 'auth_cipher',
+ 'auth_psk', 'description', 'tags', 'custom_fields', 'created', 'last_updated',
]
@@ -49,12 +51,13 @@ class WirelessLinkSerializer(NetBoxModelSerializer):
status = ChoiceField(choices=LinkStatusChoices, required=False)
interface_a = NestedInterfaceSerializer()
interface_b = NestedInterfaceSerializer()
+ tenant = NestedTenantSerializer(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)
class Meta:
model = WirelessLink
fields = [
- 'id', 'url', 'display', 'interface_a', 'interface_b', 'ssid', 'status', 'description', 'auth_type',
+ 'id', 'url', 'display', 'interface_a', 'interface_b', 'ssid', 'status', 'tenant', 'auth_type',
'auth_cipher', 'auth_psk', 'description', 'tags', 'custom_fields', 'created', 'last_updated',
]
diff --git a/netbox/wireless/api/views.py b/netbox/wireless/api/views.py
index 77a766c50..1103cec37 100644
--- a/netbox/wireless/api/views.py
+++ b/netbox/wireless/api/views.py
@@ -27,12 +27,12 @@ class WirelessLANGroupViewSet(NetBoxModelViewSet):
class WirelessLANViewSet(NetBoxModelViewSet):
- queryset = WirelessLAN.objects.prefetch_related('vlan', 'tags')
+ queryset = WirelessLAN.objects.prefetch_related('vlan', 'tenant', 'tags')
serializer_class = serializers.WirelessLANSerializer
filterset_class = filtersets.WirelessLANFilterSet
class WirelessLinkViewSet(NetBoxModelViewSet):
- queryset = WirelessLink.objects.prefetch_related('interface_a', 'interface_b', 'tags')
+ queryset = WirelessLink.objects.prefetch_related('interface_a', 'interface_b', 'tenant', 'tags')
serializer_class = serializers.WirelessLinkSerializer
filterset_class = filtersets.WirelessLinkFilterSet
diff --git a/netbox/wireless/filtersets.py b/netbox/wireless/filtersets.py
index 7b0be857b..60c4f935b 100644
--- a/netbox/wireless/filtersets.py
+++ b/netbox/wireless/filtersets.py
@@ -4,6 +4,7 @@ from django.db.models import Q
from dcim.choices import LinkStatusChoices
from ipam.models import VLAN
from netbox.filtersets import OrganizationalModelFilterSet, NetBoxModelFilterSet
+from tenancy.filtersets import TenancyFilterSet
from utilities.filters import MultiValueNumberFilter, TreeNodeMultipleChoiceFilter
from .choices import *
from .models import *
@@ -30,7 +31,7 @@ class WirelessLANGroupFilterSet(OrganizationalModelFilterSet):
fields = ['id', 'name', 'slug', 'description']
-class WirelessLANFilterSet(NetBoxModelFilterSet):
+class WirelessLANFilterSet(NetBoxModelFilterSet, TenancyFilterSet):
group_id = TreeNodeMultipleChoiceFilter(
queryset=WirelessLANGroup.objects.all(),
field_name='group',
@@ -66,7 +67,7 @@ class WirelessLANFilterSet(NetBoxModelFilterSet):
return queryset.filter(qs_filter)
-class WirelessLinkFilterSet(NetBoxModelFilterSet):
+class WirelessLinkFilterSet(NetBoxModelFilterSet, TenancyFilterSet):
interface_a_id = MultiValueNumberFilter()
interface_b_id = MultiValueNumberFilter()
status = django_filters.MultipleChoiceFilter(
diff --git a/netbox/wireless/forms/bulk_edit.py b/netbox/wireless/forms/bulk_edit.py
index 8a472e164..639a1ed1b 100644
--- a/netbox/wireless/forms/bulk_edit.py
+++ b/netbox/wireless/forms/bulk_edit.py
@@ -3,6 +3,7 @@ from django import forms
from dcim.choices import LinkStatusChoices
from ipam.models import VLAN
from netbox.forms import NetBoxModelBulkEditForm
+from tenancy.models import Tenant
from utilities.forms import add_blank_choice, DynamicModelChoiceField
from wireless.choices import *
from wireless.constants import SSID_MAX_LENGTH
@@ -47,6 +48,10 @@ class WirelessLANBulkEditForm(NetBoxModelBulkEditForm):
required=False,
label='SSID'
)
+ tenant = DynamicModelChoiceField(
+ queryset=Tenant.objects.all(),
+ required=False
+ )
description = forms.CharField(
required=False
)
@@ -65,11 +70,11 @@ class WirelessLANBulkEditForm(NetBoxModelBulkEditForm):
model = WirelessLAN
fieldsets = (
- (None, ('group', 'vlan', 'ssid', 'description')),
+ (None, ('group', 'ssid', 'vlan', 'tenant', 'description')),
('Authentication', ('auth_type', 'auth_cipher', 'auth_psk')),
)
nullable_fields = (
- 'ssid', 'group', 'vlan', 'description', 'auth_type', 'auth_cipher', 'auth_psk',
+ 'ssid', 'group', 'vlan', 'tenant', 'description', 'auth_type', 'auth_cipher', 'auth_psk',
)
@@ -83,6 +88,10 @@ class WirelessLinkBulkEditForm(NetBoxModelBulkEditForm):
choices=add_blank_choice(LinkStatusChoices),
required=False
)
+ tenant = DynamicModelChoiceField(
+ queryset=Tenant.objects.all(),
+ required=False
+ )
description = forms.CharField(
required=False
)
@@ -101,9 +110,9 @@ class WirelessLinkBulkEditForm(NetBoxModelBulkEditForm):
model = WirelessLink
fieldsets = (
- (None, ('ssid', 'status', 'description')),
+ (None, ('ssid', 'status', 'tenant', 'description')),
('Authentication', ('auth_type', 'auth_cipher', 'auth_psk'))
)
nullable_fields = (
- 'ssid', 'description', 'auth_type', 'auth_cipher', 'auth_psk',
+ 'ssid', 'tenant', 'description', 'auth_type', 'auth_cipher', 'auth_psk',
)
diff --git a/netbox/wireless/forms/bulk_import.py b/netbox/wireless/forms/bulk_import.py
index 4b8acb385..6a1ca4f36 100644
--- a/netbox/wireless/forms/bulk_import.py
+++ b/netbox/wireless/forms/bulk_import.py
@@ -2,6 +2,7 @@ from dcim.choices import LinkStatusChoices
from dcim.models import Interface
from ipam.models import VLAN
from netbox.forms import NetBoxModelCSVForm
+from tenancy.models import Tenant
from utilities.forms import CSVChoiceField, CSVModelChoiceField, SlugField
from wireless.choices import *
from wireless.models import *
@@ -40,6 +41,12 @@ class WirelessLANCSVForm(NetBoxModelCSVForm):
to_field_name='name',
help_text='Bridged VLAN'
)
+ tenant = CSVModelChoiceField(
+ queryset=Tenant.objects.all(),
+ required=False,
+ to_field_name='name',
+ help_text='Assigned tenant'
+ )
auth_type = CSVChoiceField(
choices=WirelessAuthTypeChoices,
required=False,
@@ -53,7 +60,7 @@ class WirelessLANCSVForm(NetBoxModelCSVForm):
class Meta:
model = WirelessLAN
- fields = ('ssid', 'group', 'description', 'vlan', 'auth_type', 'auth_cipher', 'auth_psk')
+ fields = ('ssid', 'group', 'vlan', 'tenant', 'description', 'auth_type', 'auth_cipher', 'auth_psk')
class WirelessLinkCSVForm(NetBoxModelCSVForm):
@@ -67,6 +74,12 @@ class WirelessLinkCSVForm(NetBoxModelCSVForm):
interface_b = CSVModelChoiceField(
queryset=Interface.objects.all()
)
+ tenant = CSVModelChoiceField(
+ queryset=Tenant.objects.all(),
+ required=False,
+ to_field_name='name',
+ help_text='Assigned tenant'
+ )
auth_type = CSVChoiceField(
choices=WirelessAuthTypeChoices,
required=False,
@@ -80,4 +93,6 @@ class WirelessLinkCSVForm(NetBoxModelCSVForm):
class Meta:
model = WirelessLink
- fields = ('interface_a', 'interface_b', 'ssid', 'description', 'auth_type', 'auth_cipher', 'auth_psk')
+ fields = (
+ 'interface_a', 'interface_b', 'ssid', 'tenant', 'description', 'auth_type', 'auth_cipher', 'auth_psk',
+ )
diff --git a/netbox/wireless/forms/filtersets.py b/netbox/wireless/forms/filtersets.py
index 8dcb48673..9e8808e17 100644
--- a/netbox/wireless/forms/filtersets.py
+++ b/netbox/wireless/forms/filtersets.py
@@ -3,6 +3,7 @@ from django.utils.translation import gettext as _
from dcim.choices import LinkStatusChoices
from netbox.forms import NetBoxModelFilterSetForm
+from tenancy.forms import TenancyFilterForm
from utilities.forms import add_blank_choice, DynamicModelMultipleChoiceField, StaticSelect, TagFilterField
from wireless.choices import *
from wireless.models import *
@@ -24,11 +25,12 @@ class WirelessLANGroupFilterForm(NetBoxModelFilterSetForm):
tag = TagFilterField(model)
-class WirelessLANFilterForm(NetBoxModelFilterSetForm):
+class WirelessLANFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
model = WirelessLAN
fieldsets = (
(None, ('q', 'tag')),
('Attributes', ('ssid', 'group_id',)),
+ ('Tenant', ('tenant_group_id', 'tenant_id')),
('Authentication', ('auth_type', 'auth_cipher', 'auth_psk')),
)
ssid = forms.CharField(
@@ -57,8 +59,14 @@ class WirelessLANFilterForm(NetBoxModelFilterSetForm):
tag = TagFilterField(model)
-class WirelessLinkFilterForm(NetBoxModelFilterSetForm):
+class WirelessLinkFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
model = WirelessLink
+ fieldsets = (
+ (None, ('q', 'tag')),
+ ('Attributes', ('ssid', 'status',)),
+ ('Tenant', ('tenant_group_id', 'tenant_id')),
+ ('Authentication', ('auth_type', 'auth_cipher', 'auth_psk')),
+ )
ssid = forms.CharField(
required=False,
label='SSID'
diff --git a/netbox/wireless/forms/models.py b/netbox/wireless/forms/models.py
index d1012ba59..bcffcf896 100644
--- a/netbox/wireless/forms/models.py
+++ b/netbox/wireless/forms/models.py
@@ -1,6 +1,7 @@
from dcim.models import Device, Interface, Location, Region, Site, SiteGroup
from ipam.models import VLAN, VLANGroup
from netbox.forms import NetBoxModelForm
+from tenancy.forms import TenancyForm
from utilities.forms import DynamicModelChoiceField, SlugField, StaticSelect
from wireless.models import *
@@ -25,7 +26,7 @@ class WirelessLANGroupForm(NetBoxModelForm):
]
-class WirelessLANForm(NetBoxModelForm):
+class WirelessLANForm(TenancyForm, NetBoxModelForm):
group = DynamicModelChoiceField(
queryset=WirelessLANGroup.objects.all(),
required=False
@@ -79,14 +80,15 @@ class WirelessLANForm(NetBoxModelForm):
fieldsets = (
('Wireless LAN', ('ssid', 'group', 'description', 'tags')),
('VLAN', ('region', 'site_group', 'site', 'vlan_group', 'vlan',)),
+ ('Tenancy', ('tenant_group', 'tenant')),
('Authentication', ('auth_type', 'auth_cipher', 'auth_psk')),
)
class Meta:
model = WirelessLAN
fields = [
- 'ssid', 'group', 'description', 'region', 'site_group', 'site', 'vlan_group', 'vlan', 'auth_type',
- 'auth_cipher', 'auth_psk', 'tags',
+ 'ssid', 'group', 'description', 'region', 'site_group', 'site', 'vlan_group', 'vlan', 'tenant_group',
+ 'tenant', 'auth_type', 'auth_cipher', 'auth_psk', 'tags',
]
widgets = {
'auth_type': StaticSelect,
@@ -94,7 +96,7 @@ class WirelessLANForm(NetBoxModelForm):
}
-class WirelessLinkForm(NetBoxModelForm):
+class WirelessLinkForm(TenancyForm, NetBoxModelForm):
site_a = DynamicModelChoiceField(
queryset=Site.objects.all(),
required=False,
@@ -180,6 +182,7 @@ class WirelessLinkForm(NetBoxModelForm):
('Side A', ('site_a', 'location_a', 'device_a', 'interface_a')),
('Side B', ('site_b', 'location_b', 'device_b', 'interface_b')),
('Link', ('status', 'ssid', 'description', 'tags')),
+ ('Tenancy', ('tenant_group', 'tenant')),
('Authentication', ('auth_type', 'auth_cipher', 'auth_psk')),
)
@@ -187,7 +190,7 @@ class WirelessLinkForm(NetBoxModelForm):
model = WirelessLink
fields = [
'site_a', 'location_a', 'device_a', 'interface_a', 'site_b', 'location_b', 'device_b', 'interface_b',
- 'status', 'ssid', 'description', 'auth_type', 'auth_cipher', 'auth_psk', 'tags',
+ 'status', 'ssid', 'tenant_group', 'tenant', 'description', 'auth_type', 'auth_cipher', 'auth_psk', 'tags',
]
widgets = {
'status': StaticSelect,
diff --git a/netbox/wireless/migrations/0004_wireless_tenancy.py b/netbox/wireless/migrations/0004_wireless_tenancy.py
new file mode 100644
index 000000000..aa5837b0a
--- /dev/null
+++ b/netbox/wireless/migrations/0004_wireless_tenancy.py
@@ -0,0 +1,25 @@
+# Generated by Django 4.0.5 on 2022-06-27 13:44
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('tenancy', '0007_contact_link'),
+ ('wireless', '0003_created_datetimefield'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='wirelesslan',
+ name='tenant',
+ field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='wireless_lans', to='tenancy.tenant'),
+ ),
+ migrations.AddField(
+ model_name='wirelesslink',
+ name='tenant',
+ field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='wireless_links', to='tenancy.tenant'),
+ ),
+ ]
diff --git a/netbox/wireless/models.py b/netbox/wireless/models.py
index 0543e5621..dd3945d50 100644
--- a/netbox/wireless/models.py
+++ b/netbox/wireless/models.py
@@ -101,6 +101,13 @@ class WirelessLAN(WirelessAuthenticationBase, NetBoxModel):
null=True,
verbose_name='VLAN'
)
+ tenant = models.ForeignKey(
+ to='tenancy.Tenant',
+ on_delete=models.PROTECT,
+ related_name='wireless_lans',
+ blank=True,
+ null=True
+ )
description = models.CharField(
max_length=200,
blank=True
@@ -143,6 +150,13 @@ class WirelessLink(WirelessAuthenticationBase, NetBoxModel):
choices=LinkStatusChoices,
default=LinkStatusChoices.STATUS_CONNECTED
)
+ tenant = models.ForeignKey(
+ to='tenancy.Tenant',
+ on_delete=models.PROTECT,
+ related_name='wireless_links',
+ blank=True,
+ null=True
+ )
description = models.CharField(
max_length=200,
blank=True
diff --git a/netbox/wireless/tables/wirelesslan.py b/netbox/wireless/tables/wirelesslan.py
index 9955d4ac4..fc791d730 100644
--- a/netbox/wireless/tables/wirelesslan.py
+++ b/netbox/wireless/tables/wirelesslan.py
@@ -2,6 +2,7 @@ import django_tables2 as tables
from dcim.models import Interface
from netbox.tables import NetBoxTable, columns
+from tenancy.tables import TenantColumn
from wireless.models import *
__all__ = (
@@ -39,6 +40,7 @@ class WirelessLANTable(NetBoxTable):
group = tables.Column(
linkify=True
)
+ tenant = TenantColumn()
interface_count = tables.Column(
verbose_name='Interfaces'
)
@@ -49,8 +51,8 @@ class WirelessLANTable(NetBoxTable):
class Meta(NetBoxTable.Meta):
model = WirelessLAN
fields = (
- 'pk', 'ssid', 'group', 'description', 'vlan', 'interface_count', 'auth_type', 'auth_cipher', 'auth_psk',
- 'tags', 'created', 'last_updated',
+ 'pk', 'ssid', 'group', 'tenant', 'description', 'vlan', 'interface_count', 'auth_type', 'auth_cipher',
+ 'auth_psk', 'tags', 'created', 'last_updated',
)
default_columns = ('pk', 'ssid', 'group', 'description', 'vlan', 'auth_type', 'interface_count')
diff --git a/netbox/wireless/tables/wirelesslink.py b/netbox/wireless/tables/wirelesslink.py
index 72037c4d9..6a45a21ae 100644
--- a/netbox/wireless/tables/wirelesslink.py
+++ b/netbox/wireless/tables/wirelesslink.py
@@ -1,6 +1,7 @@
import django_tables2 as tables
from netbox.tables import NetBoxTable, columns
+from tenancy.tables import TenantColumn
from wireless.models import *
__all__ = (
@@ -28,6 +29,7 @@ class WirelessLinkTable(NetBoxTable):
interface_b = tables.Column(
linkify=True
)
+ tenant = TenantColumn()
tags = columns.TagColumn(
url_name='wireless:wirelesslink_list'
)
@@ -35,7 +37,7 @@ class WirelessLinkTable(NetBoxTable):
class Meta(NetBoxTable.Meta):
model = WirelessLink
fields = (
- 'pk', 'id', 'status', 'device_a', 'interface_a', 'device_b', 'interface_b', 'ssid', 'description',
+ 'pk', 'id', 'status', 'device_a', 'interface_a', 'device_b', 'interface_b', 'ssid', 'tenant', 'description',
'auth_type', 'auth_cipher', 'auth_psk', 'tags', 'created', 'last_updated',
)
default_columns = (
diff --git a/netbox/wireless/tests/test_api.py b/netbox/wireless/tests/test_api.py
index 917b7b320..9ef552eb7 100644
--- a/netbox/wireless/tests/test_api.py
+++ b/netbox/wireless/tests/test_api.py
@@ -1,10 +1,11 @@
from django.urls import reverse
-from wireless.choices import *
-from wireless.models import *
from dcim.choices import InterfaceTypeChoices
from dcim.models import Interface
+from tenancy.models import Tenant
from utilities.testing import APITestCase, APIViewTestCases, create_test_device
+from wireless.choices import *
+from wireless.models import *
class AppTest(APITestCase):
@@ -52,6 +53,12 @@ class WirelessLANTest(APIViewTestCases.APIViewTestCase):
@classmethod
def setUpTestData(cls):
+ tenants = (
+ Tenant(name='Tenant 1', slug='tenant-1'),
+ Tenant(name='Tenant 2', slug='tenant-2'),
+ )
+ Tenant.objects.bulk_create(tenants)
+
groups = (
WirelessLANGroup(name='Group 1', slug='group-1'),
WirelessLANGroup(name='Group 2', slug='group-2'),
@@ -71,21 +78,25 @@ class WirelessLANTest(APIViewTestCases.APIViewTestCase):
{
'ssid': 'WLAN4',
'group': groups[0].pk,
+ 'tenant': tenants[0].pk,
'auth_type': WirelessAuthTypeChoices.TYPE_OPEN,
},
{
'ssid': 'WLAN5',
'group': groups[1].pk,
+ 'tenant': tenants[0].pk,
'auth_type': WirelessAuthTypeChoices.TYPE_WPA_PERSONAL,
},
{
'ssid': 'WLAN6',
+ 'tenant': tenants[0].pk,
'auth_type': WirelessAuthTypeChoices.TYPE_WPA_ENTERPRISE,
},
]
cls.bulk_update_data = {
'group': groups[2].pk,
+ 'tenant': tenants[1].pk,
'description': 'New description',
'auth_type': WirelessAuthTypeChoices.TYPE_WPA_PERSONAL,
'auth_cipher': WirelessAuthCipherChoices.CIPHER_AES,
@@ -115,10 +126,16 @@ class WirelessLinkTest(APIViewTestCases.APIViewTestCase):
]
Interface.objects.bulk_create(interfaces)
+ tenants = (
+ Tenant(name='Tenant 1', slug='tenant-1'),
+ Tenant(name='Tenant 2', slug='tenant-2'),
+ )
+ Tenant.objects.bulk_create(tenants)
+
wireless_links = (
- WirelessLink(ssid='LINK1', interface_a=interfaces[0], interface_b=interfaces[1]),
- WirelessLink(ssid='LINK2', interface_a=interfaces[2], interface_b=interfaces[3]),
- WirelessLink(ssid='LINK3', interface_a=interfaces[4], interface_b=interfaces[5]),
+ WirelessLink(ssid='LINK1', interface_a=interfaces[0], interface_b=interfaces[1], tenant=tenants[0]),
+ WirelessLink(ssid='LINK2', interface_a=interfaces[2], interface_b=interfaces[3], tenant=tenants[0]),
+ WirelessLink(ssid='LINK3', interface_a=interfaces[4], interface_b=interfaces[5], tenant=tenants[0]),
)
WirelessLink.objects.bulk_create(wireless_links)
@@ -127,15 +144,18 @@ class WirelessLinkTest(APIViewTestCases.APIViewTestCase):
'interface_a': interfaces[6].pk,
'interface_b': interfaces[7].pk,
'ssid': 'LINK4',
+ 'tenant': tenants[1].pk,
},
{
'interface_a': interfaces[8].pk,
'interface_b': interfaces[9].pk,
'ssid': 'LINK5',
+ 'tenant': tenants[1].pk,
},
{
'interface_a': interfaces[10].pk,
'interface_b': interfaces[11].pk,
'ssid': 'LINK6',
+ 'tenant': tenants[1].pk,
},
]
diff --git a/netbox/wireless/tests/test_filtersets.py b/netbox/wireless/tests/test_filtersets.py
index 5fee4fbf4..ffe919c32 100644
--- a/netbox/wireless/tests/test_filtersets.py
+++ b/netbox/wireless/tests/test_filtersets.py
@@ -3,6 +3,7 @@ from django.test import TestCase
from dcim.choices import InterfaceTypeChoices, LinkStatusChoices
from dcim.models import Interface
from ipam.models import VLAN
+from tenancy.models import Tenant
from wireless.choices import *
from wireless.filtersets import *
from wireless.models import *
@@ -43,10 +44,6 @@ class WirelessLANGroupTestCase(TestCase, ChangeLoggedFilterSetTests):
params = {'slug': ['wireless-lan-group-1', 'wireless-lan-group-2']}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
- def test_description(self):
- params = {'description': ['A', 'B']}
- self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
-
def test_parent(self):
parent_groups = WirelessLANGroup.objects.filter(parent__isnull=True)[:2]
params = {'parent_id': [parent_groups[0].pk, parent_groups[1].pk]}
@@ -81,10 +78,17 @@ class WirelessLANTestCase(TestCase, ChangeLoggedFilterSetTests):
)
VLAN.objects.bulk_create(vlans)
+ tenants = (
+ Tenant(name='Tenant 1', slug='tenant-1'),
+ Tenant(name='Tenant 2', slug='tenant-2'),
+ Tenant(name='Tenant 3', slug='tenant-3'),
+ )
+ Tenant.objects.bulk_create(tenants)
+
wireless_lans = (
- WirelessLAN(ssid='WLAN1', group=groups[0], vlan=vlans[0], auth_type=WirelessAuthTypeChoices.TYPE_OPEN, auth_cipher=WirelessAuthCipherChoices.CIPHER_AUTO, auth_psk='PSK1'),
- WirelessLAN(ssid='WLAN2', group=groups[1], vlan=vlans[1], auth_type=WirelessAuthTypeChoices.TYPE_WEP, auth_cipher=WirelessAuthCipherChoices.CIPHER_TKIP, auth_psk='PSK2'),
- WirelessLAN(ssid='WLAN3', group=groups[2], vlan=vlans[2], auth_type=WirelessAuthTypeChoices.TYPE_WPA_PERSONAL, auth_cipher=WirelessAuthCipherChoices.CIPHER_AES, auth_psk='PSK3'),
+ 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.objects.bulk_create(wireless_lans)
@@ -116,6 +120,13 @@ class WirelessLANTestCase(TestCase, ChangeLoggedFilterSetTests):
params = {'auth_psk': ['PSK1', 'PSK2']}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+ def test_tenant(self):
+ tenants = Tenant.objects.all()[:2]
+ params = {'tenant_id': [tenants[0].pk, tenants[1].pk]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+ params = {'tenant': [tenants[0].slug, tenants[1].slug]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
class WirelessLinkTestCase(TestCase, ChangeLoggedFilterSetTests):
queryset = WirelessLink.objects.all()
@@ -124,6 +135,13 @@ class WirelessLinkTestCase(TestCase, ChangeLoggedFilterSetTests):
@classmethod
def setUpTestData(cls):
+ tenants = (
+ Tenant(name='Tenant 1', slug='tenant-1'),
+ Tenant(name='Tenant 2', slug='tenant-2'),
+ Tenant(name='Tenant 3', slug='tenant-3'),
+ )
+ Tenant.objects.bulk_create(tenants)
+
devices = (
create_test_device('device1'),
create_test_device('device2'),
@@ -152,6 +170,7 @@ class WirelessLinkTestCase(TestCase, ChangeLoggedFilterSetTests):
auth_type=WirelessAuthTypeChoices.TYPE_OPEN,
auth_cipher=WirelessAuthCipherChoices.CIPHER_AUTO,
auth_psk='PSK1',
+ tenant=tenants[0],
description='foobar1'
).save()
WirelessLink(
@@ -162,6 +181,7 @@ class WirelessLinkTestCase(TestCase, ChangeLoggedFilterSetTests):
auth_type=WirelessAuthTypeChoices.TYPE_WEP,
auth_cipher=WirelessAuthCipherChoices.CIPHER_TKIP,
auth_psk='PSK2',
+ tenant=tenants[1],
description='foobar2'
).save()
WirelessLink(
@@ -171,7 +191,8 @@ class WirelessLinkTestCase(TestCase, ChangeLoggedFilterSetTests):
status=LinkStatusChoices.STATUS_DECOMMISSIONING,
auth_type=WirelessAuthTypeChoices.TYPE_WPA_PERSONAL,
auth_cipher=WirelessAuthCipherChoices.CIPHER_AES,
- auth_psk='PSK3'
+ auth_psk='PSK3',
+ tenant=tenants[2],
).save()
WirelessLink(
interface_a=interfaces[5],
@@ -202,3 +223,10 @@ class WirelessLinkTestCase(TestCase, ChangeLoggedFilterSetTests):
def test_description(self):
params = {'description': ['foobar1', 'foobar2']}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+ def test_tenant(self):
+ tenants = Tenant.objects.all()[:2]
+ params = {'tenant_id': [tenants[0].pk, tenants[1].pk]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+ params = {'tenant': [tenants[0].slug, tenants[1].slug]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
diff --git a/netbox/wireless/tests/test_views.py b/netbox/wireless/tests/test_views.py
index 4141af6d6..7dea17d15 100644
--- a/netbox/wireless/tests/test_views.py
+++ b/netbox/wireless/tests/test_views.py
@@ -2,6 +2,7 @@ from wireless.choices import *
from wireless.models import *
from dcim.choices import InterfaceTypeChoices, LinkStatusChoices
from dcim.models import Interface
+from tenancy.models import Tenant
from utilities.testing import ViewTestCases, create_tags, create_test_device
@@ -47,6 +48,13 @@ class WirelessLANTestCase(ViewTestCases.PrimaryObjectViewTestCase):
@classmethod
def setUpTestData(cls):
+ tenants = (
+ Tenant(name='Tenant 1', slug='tenant-1'),
+ Tenant(name='Tenant 2', slug='tenant-2'),
+ Tenant(name='Tenant 3', slug='tenant-3'),
+ )
+ Tenant.objects.bulk_create(tenants)
+
groups = (
WirelessLANGroup(name='Wireless LAN Group 1', slug='wireless-lan-group-1'),
WirelessLANGroup(name='Wireless LAN Group 2', slug='wireless-lan-group-2'),
@@ -55,9 +63,9 @@ class WirelessLANTestCase(ViewTestCases.PrimaryObjectViewTestCase):
group.save()
WirelessLAN.objects.bulk_create([
- WirelessLAN(group=groups[0], ssid='WLAN1'),
- WirelessLAN(group=groups[0], ssid='WLAN2'),
- WirelessLAN(group=groups[0], ssid='WLAN3'),
+ 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]),
])
tags = create_tags('Alpha', 'Bravo', 'Charlie')
@@ -65,14 +73,15 @@ class WirelessLANTestCase(ViewTestCases.PrimaryObjectViewTestCase):
cls.form_data = {
'ssid': 'WLAN2',
'group': groups[1].pk,
+ 'tenant': tenants[1].pk,
'tags': [t.pk for t in tags],
}
cls.csv_data = (
- "group,ssid",
- "Wireless LAN Group 2,WLAN4",
- "Wireless LAN Group 2,WLAN5",
- "Wireless LAN Group 2,WLAN6",
+ 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}",
)
cls.bulk_edit_data = {
@@ -85,6 +94,14 @@ class WirelessLinkTestCase(ViewTestCases.PrimaryObjectViewTestCase):
@classmethod
def setUpTestData(cls):
+
+ tenants = (
+ Tenant(name='Tenant 1', slug='tenant-1'),
+ Tenant(name='Tenant 2', slug='tenant-2'),
+ Tenant(name='Tenant 3', slug='tenant-3'),
+ )
+ Tenant.objects.bulk_create(tenants)
+
device = create_test_device('test-device')
interfaces = [
Interface(
@@ -98,9 +115,9 @@ class WirelessLinkTestCase(ViewTestCases.PrimaryObjectViewTestCase):
]
Interface.objects.bulk_create(interfaces)
- WirelessLink(interface_a=interfaces[0], interface_b=interfaces[1], ssid='LINK1').save()
- WirelessLink(interface_a=interfaces[2], interface_b=interfaces[3], ssid='LINK2').save()
- WirelessLink(interface_a=interfaces[4], interface_b=interfaces[5], ssid='LINK3').save()
+ WirelessLink(interface_a=interfaces[0], interface_b=interfaces[1], ssid='LINK1', tenant=tenants[0]).save()
+ WirelessLink(interface_a=interfaces[2], interface_b=interfaces[3], ssid='LINK2', tenant=tenants[0]).save()
+ WirelessLink(interface_a=interfaces[4], interface_b=interfaces[5], ssid='LINK3', tenant=tenants[0]).save()
tags = create_tags('Alpha', 'Bravo', 'Charlie')
@@ -108,14 +125,15 @@ class WirelessLinkTestCase(ViewTestCases.PrimaryObjectViewTestCase):
'interface_a': interfaces[6].pk,
'interface_b': interfaces[7].pk,
'status': LinkStatusChoices.STATUS_PLANNED,
+ 'tenant': tenants[1].pk,
'tags': [t.pk for t in tags],
}
cls.csv_data = (
- "interface_a,interface_b,status",
- f"{interfaces[6].pk},{interfaces[7].pk},connected",
- f"{interfaces[8].pk},{interfaces[9].pk},connected",
- f"{interfaces[10].pk},{interfaces[11].pk},connected",
+ f"interface_a,interface_b,status,tenant",
+ f"{interfaces[6].pk},{interfaces[7].pk},connected,{tenants[0].name}",
+ f"{interfaces[8].pk},{interfaces[9].pk},connected,{tenants[1].name}",
+ f"{interfaces[10].pk},{interfaces[11].pk},connected,{tenants[2].name}",
)
cls.bulk_edit_data = {