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

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

This commit is contained in:
jeremystretch
2022-06-27 11:30:52 -04:00
parent f6c52e0616
commit 7dd5f9e720
21 changed files with 265 additions and 83 deletions

View File

@ -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.

View File

@ -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:

View File

@ -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

View File

@ -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> <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>Cables</p> <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>

View File

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

View File

@ -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>

View File

@ -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 {

View File

@ -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', 'id', 'url', 'display', 'ssid', 'description', 'group', 'vlan', 'tenant', 'auth_type', 'auth_cipher',
'description', 'tags', 'custom_fields', 'created', 'last_updated', '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',
] ]

View File

@ -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

View File

@ -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(

View File

@ -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',
) )

View File

@ -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',
)

View File

@ -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'

View File

@ -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', 'ssid', 'group', 'description', 'region', 'site_group', 'site', 'vlan_group', 'vlan', 'tenant_group',
'auth_cipher', 'auth_psk', 'tags', '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,

View File

@ -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'),
),
]

View File

@ -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

View File

@ -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', 'pk', 'ssid', 'group', 'tenant', 'description', 'vlan', 'interface_count', 'auth_type', 'auth_cipher',
'tags', 'created', 'last_updated', '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')

View File

@ -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 = (

View File

@ -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='LINK1', interface_a=interfaces[0], interface_b=interfaces[1], tenant=tenants[0]),
WirelessLink(ssid='LINK2', interface_a=interfaces[2], interface_b=interfaces[3]), 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]), 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,
}, },
] ]

View File

@ -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='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], auth_type=WirelessAuthTypeChoices.TYPE_WEP, auth_cipher=WirelessAuthCipherChoices.CIPHER_TKIP, auth_psk='PSK2'), 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], auth_type=WirelessAuthTypeChoices.TYPE_WPA_PERSONAL, auth_cipher=WirelessAuthCipherChoices.CIPHER_AES, auth_psk='PSK3'), 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)

View File

@ -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='WLAN1', tenant=tenants[0]),
WirelessLAN(group=groups[0], ssid='WLAN2'), WirelessLAN(group=groups[0], ssid='WLAN2', tenant=tenants[0]),
WirelessLAN(group=groups[0], ssid='WLAN3'), 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", f"group,ssid,tenant",
"Wireless LAN Group 2,WLAN4", f"Wireless LAN Group 2,WLAN4,{tenants[0].name}",
"Wireless LAN Group 2,WLAN5", f"Wireless LAN Group 2,WLAN5,{tenants[1].name}",
"Wireless LAN Group 2,WLAN6", 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[0], interface_b=interfaces[1], ssid='LINK1', tenant=tenants[0]).save()
WirelessLink(interface_a=interfaces[2], interface_b=interfaces[3], ssid='LINK2').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').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"interface_a,interface_b,status,tenant",
f"{interfaces[6].pk},{interfaces[7].pk},connected", f"{interfaces[6].pk},{interfaces[7].pk},connected,{tenants[0].name}",
f"{interfaces[8].pk},{interfaces[9].pk},connected", f"{interfaces[8].pk},{interfaces[9].pk},connected,{tenants[1].name}",
f"{interfaces[10].pk},{interfaces[11].pk},connected", f"{interfaces[10].pk},{interfaces[11].pk},connected,{tenants[2].name}",
) )
cls.bulk_edit_data = { cls.bulk_edit_data = {