Prechádzať zdrojové kódy

Closes #9177: Add tenant assignment for wireless LANs & links

jeremystretch 3 rokov pred
rodič
commit
7dd5f9e720

+ 1 - 1
docs/models/wireless/wirelesslan.md

@@ -1,6 +1,6 @@
 # Wireless LANs
 # 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.
 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.
 
 

+ 1 - 1
docs/models/wireless/wirelesslink.md

@@ -1,6 +1,6 @@
 # Wireless Links
 # 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:
 Each wireless link may have authentication attributes associated with it, including:
 
 

+ 5 - 0
docs/release-notes/version-3.3.md

@@ -28,6 +28,7 @@
 * [#8495](https://github.com/netbox-community/netbox/issues/8495) - Enable custom field grouping
 * [#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
 * [#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
 * [#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
 * [#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
 * [#9582](https://github.com/netbox-community/netbox/issues/9582) - Enable assigning config contexts based on device location
 
 
@@ -70,3 +71,7 @@
     * Added `device` field
     * Added `device` field
     * The `site` field is now directly writable (rather than being inferred from the assigned cluster)
     * 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.
     * 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

+ 10 - 2
netbox/templates/tenancy/tenant.html

@@ -61,6 +61,10 @@
                     <h2><a href="{% url 'dcim:device_list' %}?tenant_id={{ object.pk }}" class="stat-btn btn {% if stats.device_count %}btn-primary{% else %}btn-outline-dark{% endif %} btn-lg">{{ stats.device_count }}</a></h2>
                     <h2><a href="{% url 'dcim:device_list' %}?tenant_id={{ object.pk }}" class="stat-btn btn {% if stats.device_count %}btn-primary{% else %}btn-outline-dark{% endif %} btn-lg">{{ stats.device_count }}</a></h2>
                     <p>Devices</p>
                     <p>Devices</p>
                 </div>
                 </div>
+                <div class="col col-md-4 text-center">
+                    <h2><a href="{% url 'dcim:cable_list' %}?tenant_id={{ object.pk }}" class="stat-btn btn {% if stats.cable_count %}btn-primary{% else %}btn-outline-dark{% endif %} btn-lg">{{ stats.cable_count }}</a></h2>
+                    <p>Cables</p>
+                </div>
                 <div class="col col-md-4 text-center">
                 <div class="col col-md-4 text-center">
                     <h2><a href="{% url 'ipam:vrf_list' %}?tenant_id={{ object.pk }}" class="stat-btn btn {% if stats.vrf_count %}btn-primary{% else %}btn-outline-dark{% endif %} btn-lg">{{ stats.vrf_count }}</a></h2>
                     <h2><a href="{% url 'ipam:vrf_list' %}?tenant_id={{ object.pk }}" class="stat-btn btn {% if stats.vrf_count %}btn-primary{% else %}btn-outline-dark{% endif %} btn-lg">{{ stats.vrf_count }}</a></h2>
                     <p>VRFs</p>
                     <p>VRFs</p>
@@ -102,8 +106,12 @@
                     <p>Clusters</p>
                     <p>Clusters</p>
                 </div>
                 </div>
                 <div class="col col-md-4 text-center">
                 <div class="col col-md-4 text-center">
-                    <h2><a href="{% url 'dcim:cable_list' %}?tenant_id={{ object.pk }}" class="stat-btn btn {% if stats.cable_count %}btn-primary{% else %}btn-outline-dark{% endif %} btn-lg">{{ stats.cable_count }}</a></h2>
-                    <p>Cables</p>
+                    <h2><a href="{% url 'wireless:wirelesslan_list' %}?tenant_id={{ object.pk }}" class="stat-btn btn {% if stats.wirelesslan_count %}btn-primary{% else %}btn-outline-dark{% endif %} btn-lg">{{ stats.wirelesslan_count }}</a></h2>
+                    <p>Wireless LANs</p>
+                </div>
+                <div class="col col-md-4 text-center">
+                    <h2><a href="{% url 'wireless:wirelesslink_list' %}?tenant_id={{ object.pk }}" class="stat-btn btn {% if stats.wirelesslink_count %}btn-primary{% else %}btn-outline-dark{% endif %} btn-lg">{{ stats.wirelesslink_count }}</a></h2>
+                    <p>Wireless Links</p>
                 </div>
                 </div>
             </div>
             </div>
         </div>
         </div>

+ 38 - 29
netbox/templates/wireless/wirelesslan.html

@@ -6,36 +6,45 @@
 {% block content %}
 {% block content %}
 <div class="row">
 <div class="row">
 	<div class="col col-md-6">
 	<div class="col col-md-6">
-        <div class="card">
-            <h5 class="card-header">Wireless LAN</h5>
-            <div class="card-body">
-                <table class="table table-hover attr-table">
-                    <tr>
-                        <th scope="row">SSID</th>
-                        <td>{{ object.ssid }}</td>
-                    </tr>
-                    <tr>
-                        <td>Group</td>
-                        <td>{{ object.group|linkify|placeholder }}</td>
-                    </tr>
-                    <tr>
-                        <th scope="row">Description</th>
-                        <td>{{ object.description|placeholder }}</td>
-                    </tr>
-                    <tr>
-                        <th scope="row">VLAN</th>
-                        <td>{{ object.vlan|linkify|placeholder }}</td>
-                    </tr>
-                </table>
-            </div>
-        </div>
-        {% include 'inc/panels/tags.html' %}
-        {% plugin_left_page object %}
+    <div class="card">
+      <h5 class="card-header">Wireless LAN</h5>
+      <div class="card-body">
+        <table class="table table-hover attr-table">
+          <tr>
+            <th scope="row">SSID</th>
+            <td>{{ object.ssid }}</td>
+          </tr>
+          <tr>
+            <td>Group</td>
+            <td>{{ object.group|linkify|placeholder }}</td>
+          </tr>
+          <tr>
+            <th scope="row">Description</th>
+            <td>{{ object.description|placeholder }}</td>
+          </tr>
+          <tr>
+            <th scope="row">VLAN</th>
+            <td>{{ object.vlan|linkify|placeholder }}</td>
+          </tr>
+          <tr>
+            <th scope="row">Tenant</th>
+            <td>
+              {% if object.tenant.group %}
+                {{ object.tenant.group|linkify }} /
+              {% endif %}
+              {{ object.tenant|linkify|placeholder }}
+            </td>
+          </tr>
+        </table>
+      </div>
     </div>
     </div>
-    <div class="col col-md-6">
-        {% 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 %}
+  </div>
+  <div class="col col-md-6">
+    {% include 'wireless/inc/authentication_attrs.html' %}
+    {% include 'inc/panels/custom_fields.html' %}
+    {% plugin_right_page object %}
 	</div>
 	</div>
 </div>
 </div>
 <div class="row">
 <div class="row">

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

@@ -23,6 +23,15 @@
               <th scope="row">SSID</th>
               <th scope="row">SSID</th>
               <td>{{ object.ssid|placeholder }}</td>
               <td>{{ object.ssid|placeholder }}</td>
             </tr>
             </tr>
+            <tr>
+              <th scope="row">Tenant</th>
+              <td>
+                {% if object.tenant.group %}
+                  {{ object.tenant.group|linkify }} /
+                {% endif %}
+                {{ object.tenant|linkify|placeholder }}
+              </td>
+            </tr>
             <tr>
             <tr>
               <th scope="row">Description</th>
               <th scope="row">Description</th>
               <td>{{ object.description|placeholder }}</td>
               <td>{{ object.description|placeholder }}</td>

+ 3 - 0
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 netbox.views import generic
 from utilities.utils import count_related
 from utilities.utils import count_related
 from virtualization.models import VirtualMachine, Cluster
 from virtualization.models import VirtualMachine, Cluster
+from wireless.models import WirelessLAN, WirelessLink
 from . import filtersets, forms, tables
 from . import filtersets, forms, tables
 from .models import *
 from .models import *
 
 
@@ -114,6 +115,8 @@ class TenantView(generic.ObjectView):
             'cluster_count': Cluster.objects.restrict(request.user, 'view').filter(tenant=instance).count(),
             'cluster_count': Cluster.objects.restrict(request.user, 'view').filter(tenant=instance).count(),
             'cable_count': Cable.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(),
             '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 {
         return {

+ 6 - 3
netbox/wireless/api/serializers.py

@@ -5,6 +5,7 @@ from dcim.api.serializers import NestedInterfaceSerializer
 from ipam.api.serializers import NestedVLANSerializer
 from ipam.api.serializers import NestedVLANSerializer
 from netbox.api import ChoiceField
 from netbox.api import ChoiceField
 from netbox.api.serializers import NestedGroupModelSerializer, NetBoxModelSerializer
 from netbox.api.serializers import NestedGroupModelSerializer, NetBoxModelSerializer
+from tenancy.api.nested_serializers import NestedTenantSerializer
 from wireless.choices import *
 from wireless.choices import *
 from wireless.models import *
 from wireless.models import *
 from .nested_serializers import *
 from .nested_serializers import *
@@ -33,14 +34,15 @@ 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)
     vlan = NestedVLANSerializer(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_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)
 
 
     class Meta:
     class Meta:
         model = WirelessLAN
         model = WirelessLAN
         fields = [
         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)
     status = ChoiceField(choices=LinkStatusChoices, required=False)
     interface_a = NestedInterfaceSerializer()
     interface_a = NestedInterfaceSerializer()
     interface_b = NestedInterfaceSerializer()
     interface_b = NestedInterfaceSerializer()
+    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)
     auth_cipher = ChoiceField(choices=WirelessAuthCipherChoices, required=False, allow_blank=True)
     auth_cipher = ChoiceField(choices=WirelessAuthCipherChoices, required=False, allow_blank=True)
 
 
     class Meta:
     class Meta:
         model = WirelessLink
         model = WirelessLink
         fields = [
         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',
             'auth_cipher', 'auth_psk', 'description', 'tags', 'custom_fields', 'created', 'last_updated',
         ]
         ]

+ 2 - 2
netbox/wireless/api/views.py

@@ -27,12 +27,12 @@ class WirelessLANGroupViewSet(NetBoxModelViewSet):
 
 
 
 
 class WirelessLANViewSet(NetBoxModelViewSet):
 class WirelessLANViewSet(NetBoxModelViewSet):
-    queryset = WirelessLAN.objects.prefetch_related('vlan', 'tags')
+    queryset = WirelessLAN.objects.prefetch_related('vlan', 'tenant', 'tags')
     serializer_class = serializers.WirelessLANSerializer
     serializer_class = serializers.WirelessLANSerializer
     filterset_class = filtersets.WirelessLANFilterSet
     filterset_class = filtersets.WirelessLANFilterSet
 
 
 
 
 class WirelessLinkViewSet(NetBoxModelViewSet):
 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
     serializer_class = serializers.WirelessLinkSerializer
     filterset_class = filtersets.WirelessLinkFilterSet
     filterset_class = filtersets.WirelessLinkFilterSet

+ 3 - 2
netbox/wireless/filtersets.py

@@ -4,6 +4,7 @@ from django.db.models import Q
 from dcim.choices import LinkStatusChoices
 from dcim.choices import LinkStatusChoices
 from ipam.models import VLAN
 from ipam.models import VLAN
 from netbox.filtersets import OrganizationalModelFilterSet, NetBoxModelFilterSet
 from netbox.filtersets import OrganizationalModelFilterSet, NetBoxModelFilterSet
+from tenancy.filtersets import TenancyFilterSet
 from utilities.filters import MultiValueNumberFilter, TreeNodeMultipleChoiceFilter
 from utilities.filters import MultiValueNumberFilter, TreeNodeMultipleChoiceFilter
 from .choices import *
 from .choices import *
 from .models import *
 from .models import *
@@ -30,7 +31,7 @@ class WirelessLANGroupFilterSet(OrganizationalModelFilterSet):
         fields = ['id', 'name', 'slug', 'description']
         fields = ['id', 'name', 'slug', 'description']
 
 
 
 
-class WirelessLANFilterSet(NetBoxModelFilterSet):
+class WirelessLANFilterSet(NetBoxModelFilterSet, TenancyFilterSet):
     group_id = TreeNodeMultipleChoiceFilter(
     group_id = TreeNodeMultipleChoiceFilter(
         queryset=WirelessLANGroup.objects.all(),
         queryset=WirelessLANGroup.objects.all(),
         field_name='group',
         field_name='group',
@@ -66,7 +67,7 @@ class WirelessLANFilterSet(NetBoxModelFilterSet):
         return queryset.filter(qs_filter)
         return queryset.filter(qs_filter)
 
 
 
 
-class WirelessLinkFilterSet(NetBoxModelFilterSet):
+class WirelessLinkFilterSet(NetBoxModelFilterSet, TenancyFilterSet):
     interface_a_id = MultiValueNumberFilter()
     interface_a_id = MultiValueNumberFilter()
     interface_b_id = MultiValueNumberFilter()
     interface_b_id = MultiValueNumberFilter()
     status = django_filters.MultipleChoiceFilter(
     status = django_filters.MultipleChoiceFilter(

+ 13 - 4
netbox/wireless/forms/bulk_edit.py

@@ -3,6 +3,7 @@ from django import forms
 from dcim.choices import LinkStatusChoices
 from dcim.choices import LinkStatusChoices
 from ipam.models import VLAN
 from ipam.models import VLAN
 from netbox.forms import NetBoxModelBulkEditForm
 from netbox.forms import NetBoxModelBulkEditForm
+from tenancy.models import Tenant
 from utilities.forms import add_blank_choice, DynamicModelChoiceField
 from utilities.forms import add_blank_choice, DynamicModelChoiceField
 from wireless.choices import *
 from wireless.choices import *
 from wireless.constants import SSID_MAX_LENGTH
 from wireless.constants import SSID_MAX_LENGTH
@@ -47,6 +48,10 @@ class WirelessLANBulkEditForm(NetBoxModelBulkEditForm):
         required=False,
         required=False,
         label='SSID'
         label='SSID'
     )
     )
+    tenant = DynamicModelChoiceField(
+        queryset=Tenant.objects.all(),
+        required=False
+    )
     description = forms.CharField(
     description = forms.CharField(
         required=False
         required=False
     )
     )
@@ -65,11 +70,11 @@ class WirelessLANBulkEditForm(NetBoxModelBulkEditForm):
 
 
     model = WirelessLAN
     model = WirelessLAN
     fieldsets = (
     fieldsets = (
-        (None, ('group', 'vlan', 'ssid', 'description')),
+        (None, ('group', 'ssid', 'vlan', 'tenant', 'description')),
         ('Authentication', ('auth_type', 'auth_cipher', 'auth_psk')),
         ('Authentication', ('auth_type', 'auth_cipher', 'auth_psk')),
     )
     )
     nullable_fields = (
     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),
         choices=add_blank_choice(LinkStatusChoices),
         required=False
         required=False
     )
     )
+    tenant = DynamicModelChoiceField(
+        queryset=Tenant.objects.all(),
+        required=False
+    )
     description = forms.CharField(
     description = forms.CharField(
         required=False
         required=False
     )
     )
@@ -101,9 +110,9 @@ class WirelessLinkBulkEditForm(NetBoxModelBulkEditForm):
 
 
     model = WirelessLink
     model = WirelessLink
     fieldsets = (
     fieldsets = (
-        (None, ('ssid', 'status', 'description')),
+        (None, ('ssid', 'status', 'tenant', 'description')),
         ('Authentication', ('auth_type', 'auth_cipher', 'auth_psk'))
         ('Authentication', ('auth_type', 'auth_cipher', 'auth_psk'))
     )
     )
     nullable_fields = (
     nullable_fields = (
-        'ssid', 'description', 'auth_type', 'auth_cipher', 'auth_psk',
+        'ssid', 'tenant', 'description', 'auth_type', 'auth_cipher', 'auth_psk',
     )
     )

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

@@ -2,6 +2,7 @@ from dcim.choices import LinkStatusChoices
 from dcim.models import Interface
 from dcim.models import Interface
 from ipam.models import VLAN
 from ipam.models import VLAN
 from netbox.forms import NetBoxModelCSVForm
 from netbox.forms import NetBoxModelCSVForm
+from tenancy.models import Tenant
 from utilities.forms import CSVChoiceField, CSVModelChoiceField, SlugField
 from utilities.forms import CSVChoiceField, CSVModelChoiceField, SlugField
 from wireless.choices import *
 from wireless.choices import *
 from wireless.models import *
 from wireless.models import *
@@ -40,6 +41,12 @@ class WirelessLANCSVForm(NetBoxModelCSVForm):
         to_field_name='name',
         to_field_name='name',
         help_text='Bridged VLAN'
         help_text='Bridged VLAN'
     )
     )
+    tenant = CSVModelChoiceField(
+        queryset=Tenant.objects.all(),
+        required=False,
+        to_field_name='name',
+        help_text='Assigned tenant'
+    )
     auth_type = CSVChoiceField(
     auth_type = CSVChoiceField(
         choices=WirelessAuthTypeChoices,
         choices=WirelessAuthTypeChoices,
         required=False,
         required=False,
@@ -53,7 +60,7 @@ class WirelessLANCSVForm(NetBoxModelCSVForm):
 
 
     class Meta:
     class Meta:
         model = WirelessLAN
         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):
 class WirelessLinkCSVForm(NetBoxModelCSVForm):
@@ -67,6 +74,12 @@ class WirelessLinkCSVForm(NetBoxModelCSVForm):
     interface_b = CSVModelChoiceField(
     interface_b = CSVModelChoiceField(
         queryset=Interface.objects.all()
         queryset=Interface.objects.all()
     )
     )
+    tenant = CSVModelChoiceField(
+        queryset=Tenant.objects.all(),
+        required=False,
+        to_field_name='name',
+        help_text='Assigned tenant'
+    )
     auth_type = CSVChoiceField(
     auth_type = CSVChoiceField(
         choices=WirelessAuthTypeChoices,
         choices=WirelessAuthTypeChoices,
         required=False,
         required=False,
@@ -80,4 +93,6 @@ class WirelessLinkCSVForm(NetBoxModelCSVForm):
 
 
     class Meta:
     class Meta:
         model = WirelessLink
         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',
+        )

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

@@ -3,6 +3,7 @@ from django.utils.translation import gettext as _
 
 
 from dcim.choices import LinkStatusChoices
 from dcim.choices import LinkStatusChoices
 from netbox.forms import NetBoxModelFilterSetForm
 from netbox.forms import NetBoxModelFilterSetForm
+from tenancy.forms import TenancyFilterForm
 from utilities.forms import add_blank_choice, DynamicModelMultipleChoiceField, StaticSelect, TagFilterField
 from utilities.forms import add_blank_choice, DynamicModelMultipleChoiceField, StaticSelect, TagFilterField
 from wireless.choices import *
 from wireless.choices import *
 from wireless.models import *
 from wireless.models import *
@@ -24,11 +25,12 @@ class WirelessLANGroupFilterForm(NetBoxModelFilterSetForm):
     tag = TagFilterField(model)
     tag = TagFilterField(model)
 
 
 
 
-class WirelessLANFilterForm(NetBoxModelFilterSetForm):
+class WirelessLANFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
     model = WirelessLAN
     model = WirelessLAN
     fieldsets = (
     fieldsets = (
         (None, ('q', 'tag')),
         (None, ('q', 'tag')),
         ('Attributes', ('ssid', 'group_id',)),
         ('Attributes', ('ssid', 'group_id',)),
+        ('Tenant', ('tenant_group_id', 'tenant_id')),
         ('Authentication', ('auth_type', 'auth_cipher', 'auth_psk')),
         ('Authentication', ('auth_type', 'auth_cipher', 'auth_psk')),
     )
     )
     ssid = forms.CharField(
     ssid = forms.CharField(
@@ -57,8 +59,14 @@ class WirelessLANFilterForm(NetBoxModelFilterSetForm):
     tag = TagFilterField(model)
     tag = TagFilterField(model)
 
 
 
 
-class WirelessLinkFilterForm(NetBoxModelFilterSetForm):
+class WirelessLinkFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
     model = WirelessLink
     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(
     ssid = forms.CharField(
         required=False,
         required=False,
         label='SSID'
         label='SSID'

+ 8 - 5
netbox/wireless/forms/models.py

@@ -1,6 +1,7 @@
 from dcim.models import Device, Interface, Location, Region, Site, SiteGroup
 from dcim.models import Device, Interface, Location, Region, Site, SiteGroup
 from ipam.models import VLAN, VLANGroup
 from ipam.models import VLAN, VLANGroup
 from netbox.forms import NetBoxModelForm
 from netbox.forms import NetBoxModelForm
+from tenancy.forms import TenancyForm
 from utilities.forms import DynamicModelChoiceField, SlugField, StaticSelect
 from utilities.forms import DynamicModelChoiceField, SlugField, StaticSelect
 from wireless.models import *
 from wireless.models import *
 
 
@@ -25,7 +26,7 @@ class WirelessLANGroupForm(NetBoxModelForm):
         ]
         ]
 
 
 
 
-class WirelessLANForm(NetBoxModelForm):
+class WirelessLANForm(TenancyForm, NetBoxModelForm):
     group = DynamicModelChoiceField(
     group = DynamicModelChoiceField(
         queryset=WirelessLANGroup.objects.all(),
         queryset=WirelessLANGroup.objects.all(),
         required=False
         required=False
@@ -79,14 +80,15 @@ class WirelessLANForm(NetBoxModelForm):
     fieldsets = (
     fieldsets = (
         ('Wireless LAN', ('ssid', 'group', 'description', 'tags')),
         ('Wireless LAN', ('ssid', 'group', 'description', 'tags')),
         ('VLAN', ('region', 'site_group', 'site', 'vlan_group', 'vlan',)),
         ('VLAN', ('region', 'site_group', 'site', 'vlan_group', 'vlan',)),
+        ('Tenancy', ('tenant_group', 'tenant')),
         ('Authentication', ('auth_type', 'auth_cipher', 'auth_psk')),
         ('Authentication', ('auth_type', 'auth_cipher', 'auth_psk')),
     )
     )
 
 
     class Meta:
     class Meta:
         model = WirelessLAN
         model = WirelessLAN
         fields = [
         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 = {
         widgets = {
             'auth_type': StaticSelect,
             'auth_type': StaticSelect,
@@ -94,7 +96,7 @@ class WirelessLANForm(NetBoxModelForm):
         }
         }
 
 
 
 
-class WirelessLinkForm(NetBoxModelForm):
+class WirelessLinkForm(TenancyForm, NetBoxModelForm):
     site_a = DynamicModelChoiceField(
     site_a = DynamicModelChoiceField(
         queryset=Site.objects.all(),
         queryset=Site.objects.all(),
         required=False,
         required=False,
@@ -180,6 +182,7 @@ class WirelessLinkForm(NetBoxModelForm):
         ('Side A', ('site_a', 'location_a', 'device_a', 'interface_a')),
         ('Side A', ('site_a', 'location_a', 'device_a', 'interface_a')),
         ('Side B', ('site_b', 'location_b', 'device_b', 'interface_b')),
         ('Side B', ('site_b', 'location_b', 'device_b', 'interface_b')),
         ('Link', ('status', 'ssid', 'description', 'tags')),
         ('Link', ('status', 'ssid', 'description', 'tags')),
+        ('Tenancy', ('tenant_group', 'tenant')),
         ('Authentication', ('auth_type', 'auth_cipher', 'auth_psk')),
         ('Authentication', ('auth_type', 'auth_cipher', 'auth_psk')),
     )
     )
 
 
@@ -187,7 +190,7 @@ class WirelessLinkForm(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', 'description', 'auth_type', 'auth_cipher', 'auth_psk', 'tags',
+            'status', 'ssid', 'tenant_group', 'tenant', 'description', 'auth_type', 'auth_cipher', 'auth_psk', 'tags',
         ]
         ]
         widgets = {
         widgets = {
             'status': StaticSelect,
             'status': StaticSelect,

+ 25 - 0
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'),
+        ),
+    ]

+ 14 - 0
netbox/wireless/models.py

@@ -101,6 +101,13 @@ class WirelessLAN(WirelessAuthenticationBase, NetBoxModel):
         null=True,
         null=True,
         verbose_name='VLAN'
         verbose_name='VLAN'
     )
     )
+    tenant = models.ForeignKey(
+        to='tenancy.Tenant',
+        on_delete=models.PROTECT,
+        related_name='wireless_lans',
+        blank=True,
+        null=True
+    )
     description = models.CharField(
     description = models.CharField(
         max_length=200,
         max_length=200,
         blank=True
         blank=True
@@ -143,6 +150,13 @@ class WirelessLink(WirelessAuthenticationBase, NetBoxModel):
         choices=LinkStatusChoices,
         choices=LinkStatusChoices,
         default=LinkStatusChoices.STATUS_CONNECTED
         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(
     description = models.CharField(
         max_length=200,
         max_length=200,
         blank=True
         blank=True

+ 4 - 2
netbox/wireless/tables/wirelesslan.py

@@ -2,6 +2,7 @@ import django_tables2 as tables
 
 
 from dcim.models import Interface
 from dcim.models import Interface
 from netbox.tables import NetBoxTable, columns
 from netbox.tables import NetBoxTable, columns
+from tenancy.tables import TenantColumn
 from wireless.models import *
 from wireless.models import *
 
 
 __all__ = (
 __all__ = (
@@ -39,6 +40,7 @@ class WirelessLANTable(NetBoxTable):
     group = tables.Column(
     group = tables.Column(
         linkify=True
         linkify=True
     )
     )
+    tenant = TenantColumn()
     interface_count = tables.Column(
     interface_count = tables.Column(
         verbose_name='Interfaces'
         verbose_name='Interfaces'
     )
     )
@@ -49,8 +51,8 @@ class WirelessLANTable(NetBoxTable):
     class Meta(NetBoxTable.Meta):
     class Meta(NetBoxTable.Meta):
         model = WirelessLAN
         model = WirelessLAN
         fields = (
         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')
         default_columns = ('pk', 'ssid', 'group', 'description', 'vlan', 'auth_type', 'interface_count')
 
 

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

@@ -1,6 +1,7 @@
 import django_tables2 as tables
 import django_tables2 as tables
 
 
 from netbox.tables import NetBoxTable, columns
 from netbox.tables import NetBoxTable, columns
+from tenancy.tables import TenantColumn
 from wireless.models import *
 from wireless.models import *
 
 
 __all__ = (
 __all__ = (
@@ -28,6 +29,7 @@ class WirelessLinkTable(NetBoxTable):
     interface_b = tables.Column(
     interface_b = tables.Column(
         linkify=True
         linkify=True
     )
     )
+    tenant = TenantColumn()
     tags = columns.TagColumn(
     tags = columns.TagColumn(
         url_name='wireless:wirelesslink_list'
         url_name='wireless:wirelesslink_list'
     )
     )
@@ -35,7 +37,7 @@ class WirelessLinkTable(NetBoxTable):
     class Meta(NetBoxTable.Meta):
     class Meta(NetBoxTable.Meta):
         model = WirelessLink
         model = WirelessLink
         fields = (
         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',
             'auth_type', 'auth_cipher', 'auth_psk', 'tags', 'created', 'last_updated',
         )
         )
         default_columns = (
         default_columns = (

+ 25 - 5
netbox/wireless/tests/test_api.py

@@ -1,10 +1,11 @@
 from django.urls import reverse
 from django.urls import reverse
 
 
-from wireless.choices import *
-from wireless.models import *
 from dcim.choices import InterfaceTypeChoices
 from dcim.choices import InterfaceTypeChoices
 from dcim.models import Interface
 from dcim.models import Interface
+from tenancy.models import Tenant
 from utilities.testing import APITestCase, APIViewTestCases, create_test_device
 from utilities.testing import APITestCase, APIViewTestCases, create_test_device
+from wireless.choices import *
+from wireless.models import *
 
 
 
 
 class AppTest(APITestCase):
 class AppTest(APITestCase):
@@ -52,6 +53,12 @@ class WirelessLANTest(APIViewTestCases.APIViewTestCase):
     @classmethod
     @classmethod
     def setUpTestData(cls):
     def setUpTestData(cls):
 
 
+        tenants = (
+            Tenant(name='Tenant 1', slug='tenant-1'),
+            Tenant(name='Tenant 2', slug='tenant-2'),
+        )
+        Tenant.objects.bulk_create(tenants)
+
         groups = (
         groups = (
             WirelessLANGroup(name='Group 1', slug='group-1'),
             WirelessLANGroup(name='Group 1', slug='group-1'),
             WirelessLANGroup(name='Group 2', slug='group-2'),
             WirelessLANGroup(name='Group 2', slug='group-2'),
@@ -71,21 +78,25 @@ class WirelessLANTest(APIViewTestCases.APIViewTestCase):
             {
             {
                 'ssid': 'WLAN4',
                 'ssid': 'WLAN4',
                 'group': groups[0].pk,
                 'group': groups[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,
+                'tenant': tenants[0].pk,
                 'auth_type': WirelessAuthTypeChoices.TYPE_WPA_PERSONAL,
                 'auth_type': WirelessAuthTypeChoices.TYPE_WPA_PERSONAL,
             },
             },
             {
             {
                 'ssid': 'WLAN6',
                 'ssid': 'WLAN6',
+                'tenant': tenants[0].pk,
                 'auth_type': WirelessAuthTypeChoices.TYPE_WPA_ENTERPRISE,
                 'auth_type': WirelessAuthTypeChoices.TYPE_WPA_ENTERPRISE,
             },
             },
         ]
         ]
 
 
         cls.bulk_update_data = {
         cls.bulk_update_data = {
             'group': groups[2].pk,
             'group': groups[2].pk,
+            'tenant': tenants[1].pk,
             'description': 'New description',
             'description': 'New description',
             'auth_type': WirelessAuthTypeChoices.TYPE_WPA_PERSONAL,
             'auth_type': WirelessAuthTypeChoices.TYPE_WPA_PERSONAL,
             'auth_cipher': WirelessAuthCipherChoices.CIPHER_AES,
             'auth_cipher': WirelessAuthCipherChoices.CIPHER_AES,
@@ -115,10 +126,16 @@ class WirelessLinkTest(APIViewTestCases.APIViewTestCase):
         ]
         ]
         Interface.objects.bulk_create(interfaces)
         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 = (
         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)
         WirelessLink.objects.bulk_create(wireless_links)
 
 
@@ -127,15 +144,18 @@ class WirelessLinkTest(APIViewTestCases.APIViewTestCase):
                 'interface_a': interfaces[6].pk,
                 'interface_a': interfaces[6].pk,
                 'interface_b': interfaces[7].pk,
                 'interface_b': interfaces[7].pk,
                 'ssid': 'LINK4',
                 'ssid': 'LINK4',
+                'tenant': tenants[1].pk,
             },
             },
             {
             {
                 'interface_a': interfaces[8].pk,
                 'interface_a': interfaces[8].pk,
                 'interface_b': interfaces[9].pk,
                 'interface_b': interfaces[9].pk,
                 'ssid': 'LINK5',
                 'ssid': 'LINK5',
+                'tenant': tenants[1].pk,
             },
             },
             {
             {
                 'interface_a': interfaces[10].pk,
                 'interface_a': interfaces[10].pk,
                 'interface_b': interfaces[11].pk,
                 'interface_b': interfaces[11].pk,
                 'ssid': 'LINK6',
                 'ssid': 'LINK6',
+                'tenant': tenants[1].pk,
             },
             },
         ]
         ]

+ 36 - 8
netbox/wireless/tests/test_filtersets.py

@@ -3,6 +3,7 @@ from django.test import TestCase
 from dcim.choices import InterfaceTypeChoices, LinkStatusChoices
 from dcim.choices import InterfaceTypeChoices, LinkStatusChoices
 from dcim.models import Interface
 from dcim.models import Interface
 from ipam.models import VLAN
 from ipam.models import VLAN
+from tenancy.models import Tenant
 from wireless.choices import *
 from wireless.choices import *
 from wireless.filtersets import *
 from wireless.filtersets import *
 from wireless.models import *
 from wireless.models import *
@@ -43,10 +44,6 @@ class WirelessLANGroupTestCase(TestCase, ChangeLoggedFilterSetTests):
         params = {'slug': ['wireless-lan-group-1', 'wireless-lan-group-2']}
         params = {'slug': ['wireless-lan-group-1', 'wireless-lan-group-2']}
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 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):
     def test_parent(self):
         parent_groups = WirelessLANGroup.objects.filter(parent__isnull=True)[:2]
         parent_groups = WirelessLANGroup.objects.filter(parent__isnull=True)[:2]
         params = {'parent_id': [parent_groups[0].pk, parent_groups[1].pk]}
         params = {'parent_id': [parent_groups[0].pk, parent_groups[1].pk]}
@@ -81,10 +78,17 @@ class WirelessLANTestCase(TestCase, ChangeLoggedFilterSetTests):
         )
         )
         VLAN.objects.bulk_create(vlans)
         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 = (
         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)
         WirelessLAN.objects.bulk_create(wireless_lans)
 
 
@@ -116,6 +120,13 @@ class WirelessLANTestCase(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_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):
 class WirelessLinkTestCase(TestCase, ChangeLoggedFilterSetTests):
     queryset = WirelessLink.objects.all()
     queryset = WirelessLink.objects.all()
@@ -124,6 +135,13 @@ class WirelessLinkTestCase(TestCase, ChangeLoggedFilterSetTests):
     @classmethod
     @classmethod
     def setUpTestData(cls):
     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 = (
         devices = (
             create_test_device('device1'),
             create_test_device('device1'),
             create_test_device('device2'),
             create_test_device('device2'),
@@ -152,6 +170,7 @@ class WirelessLinkTestCase(TestCase, ChangeLoggedFilterSetTests):
             auth_type=WirelessAuthTypeChoices.TYPE_OPEN,
             auth_type=WirelessAuthTypeChoices.TYPE_OPEN,
             auth_cipher=WirelessAuthCipherChoices.CIPHER_AUTO,
             auth_cipher=WirelessAuthCipherChoices.CIPHER_AUTO,
             auth_psk='PSK1',
             auth_psk='PSK1',
+            tenant=tenants[0],
             description='foobar1'
             description='foobar1'
         ).save()
         ).save()
         WirelessLink(
         WirelessLink(
@@ -162,6 +181,7 @@ class WirelessLinkTestCase(TestCase, ChangeLoggedFilterSetTests):
             auth_type=WirelessAuthTypeChoices.TYPE_WEP,
             auth_type=WirelessAuthTypeChoices.TYPE_WEP,
             auth_cipher=WirelessAuthCipherChoices.CIPHER_TKIP,
             auth_cipher=WirelessAuthCipherChoices.CIPHER_TKIP,
             auth_psk='PSK2',
             auth_psk='PSK2',
+            tenant=tenants[1],
             description='foobar2'
             description='foobar2'
         ).save()
         ).save()
         WirelessLink(
         WirelessLink(
@@ -171,7 +191,8 @@ class WirelessLinkTestCase(TestCase, ChangeLoggedFilterSetTests):
             status=LinkStatusChoices.STATUS_DECOMMISSIONING,
             status=LinkStatusChoices.STATUS_DECOMMISSIONING,
             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',
+            tenant=tenants[2],
         ).save()
         ).save()
         WirelessLink(
         WirelessLink(
             interface_a=interfaces[5],
             interface_a=interfaces[5],
@@ -202,3 +223,10 @@ class WirelessLinkTestCase(TestCase, ChangeLoggedFilterSetTests):
     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)
+
+    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)

+ 32 - 14
netbox/wireless/tests/test_views.py

@@ -2,6 +2,7 @@ from wireless.choices import *
 from wireless.models import *
 from wireless.models import *
 from dcim.choices import InterfaceTypeChoices, LinkStatusChoices
 from dcim.choices import InterfaceTypeChoices, LinkStatusChoices
 from dcim.models import Interface
 from dcim.models import Interface
+from tenancy.models import Tenant
 from utilities.testing import ViewTestCases, create_tags, create_test_device
 from utilities.testing import ViewTestCases, create_tags, create_test_device
 
 
 
 
@@ -47,6 +48,13 @@ class WirelessLANTestCase(ViewTestCases.PrimaryObjectViewTestCase):
     @classmethod
     @classmethod
     def setUpTestData(cls):
     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 = (
         groups = (
             WirelessLANGroup(name='Wireless LAN Group 1', slug='wireless-lan-group-1'),
             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 2', slug='wireless-lan-group-2'),
@@ -55,9 +63,9 @@ class WirelessLANTestCase(ViewTestCases.PrimaryObjectViewTestCase):
             group.save()
             group.save()
 
 
         WirelessLAN.objects.bulk_create([
         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')
         tags = create_tags('Alpha', 'Bravo', 'Charlie')
@@ -65,14 +73,15 @@ class WirelessLANTestCase(ViewTestCases.PrimaryObjectViewTestCase):
         cls.form_data = {
         cls.form_data = {
             'ssid': 'WLAN2',
             'ssid': 'WLAN2',
             'group': groups[1].pk,
             'group': groups[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 = (
-            "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 = {
         cls.bulk_edit_data = {
@@ -85,6 +94,14 @@ class WirelessLinkTestCase(ViewTestCases.PrimaryObjectViewTestCase):
 
 
     @classmethod
     @classmethod
     def setUpTestData(cls):
     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')
         device = create_test_device('test-device')
         interfaces = [
         interfaces = [
             Interface(
             Interface(
@@ -98,9 +115,9 @@ class WirelessLinkTestCase(ViewTestCases.PrimaryObjectViewTestCase):
         ]
         ]
         Interface.objects.bulk_create(interfaces)
         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')
         tags = create_tags('Alpha', 'Bravo', 'Charlie')
 
 
@@ -108,14 +125,15 @@ 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,
+            'tenant': tenants[1].pk,
             'tags': [t.pk for t in tags],
             'tags': [t.pk for t in tags],
         }
         }
 
 
         cls.csv_data = (
         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 = {
         cls.bulk_edit_data = {