mirror of
https://github.com/netbox-community/netbox.git
synced 2025-07-14 01:41:22 -06:00
10711 Add Scope to WirelessLAN (#17877)
* 7699 Add Scope to Cluster * 7699 Serializer * 7699 filterset * 7699 bulk_edit * 7699 bulk_import * 7699 model_form * 7699 graphql, tables * 7699 fixes * 7699 fixes * 7699 fixes * 7699 fixes * 7699 fix tests * 7699 fix graphql tests for clusters reference * 7699 fix dcim tests * 7699 fix ipam tests * 7699 fix tests * 7699 use mixin for model * 7699 change mixin name * 7699 scope form * 7699 scope form * 7699 scoped form, fitlerset * 7699 review changes * 7699 move ScopedFilterset * 7699 move CachedScopeMixin * 7699 review changes * 10711 Add Scope to WirelessLAN * 10711 Add Scope to WirelessLAN * 10711 Add Scope to WirelessLAN * 10711 Add Scope to WirelessLAN * 10711 Add Scope to WirelessLAN * 7699 review changes * 7699 refactor mixins * 7699 _sitegroup -> _site_group * 7699 update docstring * fix model * remove old constants, update filtersets * 10711 fix GraphQL * 10711 fix API * 10711 add tests * 10711 review changes * 10711 add tests * 10711 add scope to detail template * 10711 add api test * Extend CSV test data --------- Co-authored-by: Jeremy Stretch <jstretch@netboxlabs.com>
This commit is contained in:
parent
4bba92617d
commit
812ce8471a
@ -43,3 +43,7 @@ The security cipher used to apply wireless authentication. Options include:
|
|||||||
### Pre-Shared Key
|
### Pre-Shared Key
|
||||||
|
|
||||||
The security key configured on each client to grant access to the secured wireless LAN. This applies only to certain authentication types.
|
The security key configured on each client to grant access to the secured wireless LAN. This applies only to certain authentication types.
|
||||||
|
|
||||||
|
### Scope
|
||||||
|
|
||||||
|
The [region](../dcim/region.md), [site](../dcim/site.md), [site group](../dcim/sitegroup.md) or [location](../dcim/location.md) with which this wireless LAN is associated.
|
||||||
|
@ -22,6 +22,14 @@
|
|||||||
<th scope="row">{% trans "Status" %}</th>
|
<th scope="row">{% trans "Status" %}</th>
|
||||||
<td>{% badge object.get_status_display bg_color=object.get_status_color %}</td>
|
<td>{% badge object.get_status_display bg_color=object.get_status_color %}</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th scope="row">{% trans "Scope" %}</th>
|
||||||
|
{% if object.scope %}
|
||||||
|
<td>{{ object.scope|linkify }} ({% trans object.scope_type.name %})</td>
|
||||||
|
{% else %}
|
||||||
|
<td>{{ ''|placeholder }}</td>
|
||||||
|
{% endif %}
|
||||||
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="row">{% trans "Description" %}</th>
|
<th scope="row">{% trans "Description" %}</th>
|
||||||
<td>{{ object.description|placeholder }}</td>
|
<td>{{ object.description|placeholder }}</td>
|
||||||
|
@ -1,9 +1,13 @@
|
|||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
|
||||||
|
from dcim.constants import LOCATION_SCOPE_TYPES
|
||||||
|
from django.contrib.contenttypes.models import ContentType
|
||||||
|
from drf_spectacular.utils import extend_schema_field
|
||||||
from ipam.api.serializers_.vlans import VLANSerializer
|
from ipam.api.serializers_.vlans import VLANSerializer
|
||||||
from netbox.api.fields import ChoiceField
|
from netbox.api.fields import ChoiceField, ContentTypeField
|
||||||
from netbox.api.serializers import NestedGroupModelSerializer, NetBoxModelSerializer
|
from netbox.api.serializers import NestedGroupModelSerializer, NetBoxModelSerializer
|
||||||
from tenancy.api.serializers_.tenants import TenantSerializer
|
from tenancy.api.serializers_.tenants import TenantSerializer
|
||||||
|
from utilities.api import get_serializer_for_model
|
||||||
from wireless.choices import *
|
from wireless.choices import *
|
||||||
from wireless.models import WirelessLAN, WirelessLANGroup
|
from wireless.models import WirelessLAN, WirelessLANGroup
|
||||||
from .nested import NestedWirelessLANGroupSerializer
|
from .nested import NestedWirelessLANGroupSerializer
|
||||||
@ -34,12 +38,30 @@ class WirelessLANSerializer(NetBoxModelSerializer):
|
|||||||
tenant = TenantSerializer(nested=True, required=False, allow_null=True)
|
tenant = TenantSerializer(nested=True, 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)
|
||||||
|
scope_type = ContentTypeField(
|
||||||
|
queryset=ContentType.objects.filter(
|
||||||
|
model__in=LOCATION_SCOPE_TYPES
|
||||||
|
),
|
||||||
|
allow_null=True,
|
||||||
|
required=False,
|
||||||
|
default=None
|
||||||
|
)
|
||||||
|
scope_id = serializers.IntegerField(allow_null=True, required=False, default=None)
|
||||||
|
scope = serializers.SerializerMethodField(read_only=True)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = WirelessLAN
|
model = WirelessLAN
|
||||||
fields = [
|
fields = [
|
||||||
'id', 'url', 'display_url', 'display', 'ssid', 'description', 'group', 'status', 'vlan', 'tenant',
|
'id', 'url', 'display_url', 'display', 'ssid', 'description', 'group', 'status', 'vlan', 'scope_type', 'scope_id', 'scope',
|
||||||
'auth_type', 'auth_cipher', 'auth_psk', 'description', 'comments', 'tags', 'custom_fields',
|
'tenant', 'auth_type', 'auth_cipher', 'auth_psk', 'description', 'comments', 'tags', 'custom_fields',
|
||||||
'created', 'last_updated',
|
'created', 'last_updated',
|
||||||
]
|
]
|
||||||
brief_fields = ('id', 'url', 'display', 'ssid', 'description')
|
brief_fields = ('id', 'url', 'display', 'ssid', 'description')
|
||||||
|
|
||||||
|
@extend_schema_field(serializers.JSONField(allow_null=True))
|
||||||
|
def get_scope(self, obj):
|
||||||
|
if obj.scope_id is None:
|
||||||
|
return None
|
||||||
|
serializer = get_serializer_for_model(obj.scope)
|
||||||
|
context = {'request': self.context['request']}
|
||||||
|
return serializer(obj.scope, nested=True, context=context).data
|
||||||
|
@ -2,6 +2,7 @@ import django_filters
|
|||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
|
|
||||||
from dcim.choices import LinkStatusChoices
|
from dcim.choices import LinkStatusChoices
|
||||||
|
from dcim.filtersets import ScopedFilterSet
|
||||||
from dcim.models import Interface
|
from dcim.models import Interface
|
||||||
from ipam.models import VLAN
|
from ipam.models import VLAN
|
||||||
from netbox.filtersets import OrganizationalModelFilterSet, NetBoxModelFilterSet
|
from netbox.filtersets import OrganizationalModelFilterSet, NetBoxModelFilterSet
|
||||||
@ -43,7 +44,7 @@ class WirelessLANGroupFilterSet(OrganizationalModelFilterSet):
|
|||||||
fields = ('id', 'name', 'slug', 'description')
|
fields = ('id', 'name', 'slug', 'description')
|
||||||
|
|
||||||
|
|
||||||
class WirelessLANFilterSet(NetBoxModelFilterSet, TenancyFilterSet):
|
class WirelessLANFilterSet(NetBoxModelFilterSet, ScopedFilterSet, TenancyFilterSet):
|
||||||
group_id = TreeNodeMultipleChoiceFilter(
|
group_id = TreeNodeMultipleChoiceFilter(
|
||||||
queryset=WirelessLANGroup.objects.all(),
|
queryset=WirelessLANGroup.objects.all(),
|
||||||
field_name='group',
|
field_name='group',
|
||||||
@ -74,7 +75,7 @@ class WirelessLANFilterSet(NetBoxModelFilterSet, TenancyFilterSet):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = WirelessLAN
|
model = WirelessLAN
|
||||||
fields = ('id', 'ssid', 'auth_psk', 'description')
|
fields = ('id', 'ssid', 'auth_psk', 'scope_id', 'description')
|
||||||
|
|
||||||
def search(self, queryset, name, value):
|
def search(self, queryset, name, value):
|
||||||
if not value.strip():
|
if not value.strip():
|
||||||
|
@ -2,6 +2,7 @@ from django import forms
|
|||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
from dcim.choices import LinkStatusChoices
|
from dcim.choices import LinkStatusChoices
|
||||||
|
from dcim.forms.mixins import ScopedBulkEditForm
|
||||||
from ipam.models import VLAN
|
from ipam.models import VLAN
|
||||||
from netbox.choices import *
|
from netbox.choices import *
|
||||||
from netbox.forms import NetBoxModelBulkEditForm
|
from netbox.forms import NetBoxModelBulkEditForm
|
||||||
@ -39,7 +40,7 @@ class WirelessLANGroupBulkEditForm(NetBoxModelBulkEditForm):
|
|||||||
nullable_fields = ('parent', 'description')
|
nullable_fields = ('parent', 'description')
|
||||||
|
|
||||||
|
|
||||||
class WirelessLANBulkEditForm(NetBoxModelBulkEditForm):
|
class WirelessLANBulkEditForm(ScopedBulkEditForm, NetBoxModelBulkEditForm):
|
||||||
status = forms.ChoiceField(
|
status = forms.ChoiceField(
|
||||||
label=_('Status'),
|
label=_('Status'),
|
||||||
choices=add_blank_choice(WirelessLANStatusChoices),
|
choices=add_blank_choice(WirelessLANStatusChoices),
|
||||||
@ -89,10 +90,11 @@ class WirelessLANBulkEditForm(NetBoxModelBulkEditForm):
|
|||||||
model = WirelessLAN
|
model = WirelessLAN
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
FieldSet('group', 'ssid', 'status', 'vlan', 'tenant', 'description'),
|
FieldSet('group', 'ssid', 'status', 'vlan', 'tenant', 'description'),
|
||||||
|
FieldSet('scope_type', 'scope', name=_('Scope')),
|
||||||
FieldSet('auth_type', 'auth_cipher', 'auth_psk', name=_('Authentication')),
|
FieldSet('auth_type', 'auth_cipher', 'auth_psk', name=_('Authentication')),
|
||||||
)
|
)
|
||||||
nullable_fields = (
|
nullable_fields = (
|
||||||
'ssid', 'group', 'vlan', 'tenant', 'description', 'auth_type', 'auth_cipher', 'auth_psk', 'comments',
|
'ssid', 'group', 'vlan', 'tenant', 'description', 'auth_type', 'auth_cipher', 'auth_psk', 'scope', 'comments',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,12 +1,13 @@
|
|||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
from dcim.choices import LinkStatusChoices
|
from dcim.choices import LinkStatusChoices
|
||||||
|
from dcim.forms.mixins import ScopedImportForm
|
||||||
from dcim.models import Interface
|
from dcim.models import Interface
|
||||||
from ipam.models import VLAN
|
from ipam.models import VLAN
|
||||||
from netbox.choices import *
|
from netbox.choices import *
|
||||||
from netbox.forms import NetBoxModelImportForm
|
from netbox.forms import NetBoxModelImportForm
|
||||||
from tenancy.models import Tenant
|
from tenancy.models import Tenant
|
||||||
from utilities.forms.fields import CSVChoiceField, CSVModelChoiceField, SlugField
|
from utilities.forms.fields import CSVChoiceField, CSVModelChoiceField, SlugField
|
||||||
from wireless.choices import *
|
from wireless.choices import *
|
||||||
from wireless.models import *
|
from wireless.models import *
|
||||||
|
|
||||||
@ -32,7 +33,7 @@ class WirelessLANGroupImportForm(NetBoxModelImportForm):
|
|||||||
fields = ('name', 'slug', 'parent', 'description', 'tags')
|
fields = ('name', 'slug', 'parent', 'description', 'tags')
|
||||||
|
|
||||||
|
|
||||||
class WirelessLANImportForm(NetBoxModelImportForm):
|
class WirelessLANImportForm(ScopedImportForm, NetBoxModelImportForm):
|
||||||
group = CSVModelChoiceField(
|
group = CSVModelChoiceField(
|
||||||
label=_('Group'),
|
label=_('Group'),
|
||||||
queryset=WirelessLANGroup.objects.all(),
|
queryset=WirelessLANGroup.objects.all(),
|
||||||
@ -75,9 +76,12 @@ class WirelessLANImportForm(NetBoxModelImportForm):
|
|||||||
class Meta:
|
class Meta:
|
||||||
model = WirelessLAN
|
model = WirelessLAN
|
||||||
fields = (
|
fields = (
|
||||||
'ssid', 'group', 'status', 'vlan', 'tenant', 'auth_type', 'auth_cipher', 'auth_psk', 'description',
|
'ssid', 'group', 'status', 'vlan', 'tenant', 'auth_type', 'auth_cipher', 'auth_psk', 'scope_type', 'scope_id',
|
||||||
'comments', 'tags',
|
'description', 'comments', 'tags',
|
||||||
)
|
)
|
||||||
|
labels = {
|
||||||
|
'scope_id': _('Scope ID'),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
class WirelessLinkImportForm(NetBoxModelImportForm):
|
class WirelessLinkImportForm(NetBoxModelImportForm):
|
||||||
|
@ -2,6 +2,7 @@ from django import forms
|
|||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
from dcim.choices import LinkStatusChoices
|
from dcim.choices import LinkStatusChoices
|
||||||
|
from dcim.models import Location, Region, Site, SiteGroup
|
||||||
from netbox.choices import *
|
from netbox.choices import *
|
||||||
from netbox.forms import NetBoxModelFilterSetForm
|
from netbox.forms import NetBoxModelFilterSetForm
|
||||||
from tenancy.forms import TenancyFilterForm
|
from tenancy.forms import TenancyFilterForm
|
||||||
@ -33,6 +34,7 @@ class WirelessLANFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
|
|||||||
fieldsets = (
|
fieldsets = (
|
||||||
FieldSet('q', 'filter_id', 'tag'),
|
FieldSet('q', 'filter_id', 'tag'),
|
||||||
FieldSet('ssid', 'group_id', 'status', name=_('Attributes')),
|
FieldSet('ssid', 'group_id', 'status', name=_('Attributes')),
|
||||||
|
FieldSet('region_id', 'site_group_id', 'site_id', 'location_id', name=_('Scope')),
|
||||||
FieldSet('tenant_group_id', 'tenant_id', name=_('Tenant')),
|
FieldSet('tenant_group_id', 'tenant_id', name=_('Tenant')),
|
||||||
FieldSet('auth_type', 'auth_cipher', 'auth_psk', name=_('Authentication')),
|
FieldSet('auth_type', 'auth_cipher', 'auth_psk', name=_('Authentication')),
|
||||||
)
|
)
|
||||||
@ -65,6 +67,31 @@ class WirelessLANFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
|
|||||||
label=_('Pre-shared key'),
|
label=_('Pre-shared key'),
|
||||||
required=False
|
required=False
|
||||||
)
|
)
|
||||||
|
region_id = DynamicModelMultipleChoiceField(
|
||||||
|
queryset=Region.objects.all(),
|
||||||
|
required=False,
|
||||||
|
label=_('Region')
|
||||||
|
)
|
||||||
|
site_group_id = DynamicModelMultipleChoiceField(
|
||||||
|
queryset=SiteGroup.objects.all(),
|
||||||
|
required=False,
|
||||||
|
label=_('Site group')
|
||||||
|
)
|
||||||
|
site_id = DynamicModelMultipleChoiceField(
|
||||||
|
queryset=Site.objects.all(),
|
||||||
|
required=False,
|
||||||
|
null_option='None',
|
||||||
|
query_params={
|
||||||
|
'region_id': '$region_id',
|
||||||
|
'site_group_id': '$site_group_id',
|
||||||
|
},
|
||||||
|
label=_('Site')
|
||||||
|
)
|
||||||
|
location_id = DynamicModelMultipleChoiceField(
|
||||||
|
queryset=Location.objects.all(),
|
||||||
|
required=False,
|
||||||
|
label=_('Location')
|
||||||
|
)
|
||||||
tag = TagFilterField(model)
|
tag = TagFilterField(model)
|
||||||
|
|
||||||
|
|
||||||
|
@ -2,6 +2,7 @@ from django.forms import PasswordInput
|
|||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
from dcim.models import Device, Interface, Location, Site
|
from dcim.models import Device, Interface, Location, Site
|
||||||
|
from dcim.forms.mixins import ScopedForm
|
||||||
from ipam.models import VLAN
|
from ipam.models import VLAN
|
||||||
from netbox.forms import NetBoxModelForm
|
from netbox.forms import NetBoxModelForm
|
||||||
from tenancy.forms import TenancyForm
|
from tenancy.forms import TenancyForm
|
||||||
@ -35,7 +36,7 @@ class WirelessLANGroupForm(NetBoxModelForm):
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class WirelessLANForm(TenancyForm, NetBoxModelForm):
|
class WirelessLANForm(ScopedForm, TenancyForm, NetBoxModelForm):
|
||||||
group = DynamicModelChoiceField(
|
group = DynamicModelChoiceField(
|
||||||
label=_('Group'),
|
label=_('Group'),
|
||||||
queryset=WirelessLANGroup.objects.all(),
|
queryset=WirelessLANGroup.objects.all(),
|
||||||
@ -51,6 +52,7 @@ class WirelessLANForm(TenancyForm, NetBoxModelForm):
|
|||||||
|
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
FieldSet('ssid', 'group', 'vlan', 'status', 'description', 'tags', name=_('Wireless LAN')),
|
FieldSet('ssid', 'group', 'vlan', 'status', 'description', 'tags', name=_('Wireless LAN')),
|
||||||
|
FieldSet('scope_type', 'scope', name=_('Scope')),
|
||||||
FieldSet('tenant_group', 'tenant', name=_('Tenancy')),
|
FieldSet('tenant_group', 'tenant', name=_('Tenancy')),
|
||||||
FieldSet('auth_type', 'auth_cipher', 'auth_psk', name=_('Authentication')),
|
FieldSet('auth_type', 'auth_cipher', 'auth_psk', name=_('Authentication')),
|
||||||
)
|
)
|
||||||
@ -59,7 +61,7 @@ class WirelessLANForm(TenancyForm, NetBoxModelForm):
|
|||||||
model = WirelessLAN
|
model = WirelessLAN
|
||||||
fields = [
|
fields = [
|
||||||
'ssid', 'group', 'status', 'vlan', 'tenant_group', 'tenant', 'auth_type', 'auth_cipher', 'auth_psk',
|
'ssid', 'group', 'status', 'vlan', 'tenant_group', 'tenant', 'auth_type', 'auth_cipher', 'auth_psk',
|
||||||
'description', 'comments', 'tags',
|
'scope_type', 'description', 'comments', 'tags',
|
||||||
]
|
]
|
||||||
widgets = {
|
widgets = {
|
||||||
'auth_psk': PasswordInput(
|
'auth_psk': PasswordInput(
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
from typing import Annotated, List
|
from typing import Annotated, List, Union
|
||||||
|
|
||||||
import strawberry
|
import strawberry
|
||||||
import strawberry_django
|
import strawberry_django
|
||||||
@ -28,7 +28,7 @@ class WirelessLANGroupType(OrganizationalObjectType):
|
|||||||
|
|
||||||
@strawberry_django.type(
|
@strawberry_django.type(
|
||||||
models.WirelessLAN,
|
models.WirelessLAN,
|
||||||
fields='__all__',
|
exclude=('scope_type', 'scope_id', '_location', '_region', '_site', '_site_group'),
|
||||||
filters=WirelessLANFilter
|
filters=WirelessLANFilter
|
||||||
)
|
)
|
||||||
class WirelessLANType(NetBoxObjectType):
|
class WirelessLANType(NetBoxObjectType):
|
||||||
@ -38,6 +38,15 @@ class WirelessLANType(NetBoxObjectType):
|
|||||||
|
|
||||||
interfaces: List[Annotated["InterfaceType", strawberry.lazy('dcim.graphql.types')]]
|
interfaces: List[Annotated["InterfaceType", strawberry.lazy('dcim.graphql.types')]]
|
||||||
|
|
||||||
|
@strawberry_django.field
|
||||||
|
def scope(self) -> Annotated[Union[
|
||||||
|
Annotated["LocationType", strawberry.lazy('dcim.graphql.types')],
|
||||||
|
Annotated["RegionType", strawberry.lazy('dcim.graphql.types')],
|
||||||
|
Annotated["SiteGroupType", strawberry.lazy('dcim.graphql.types')],
|
||||||
|
Annotated["SiteType", strawberry.lazy('dcim.graphql.types')],
|
||||||
|
], strawberry.union("WirelessLANScopeType")] | None:
|
||||||
|
return self.scope
|
||||||
|
|
||||||
|
|
||||||
@strawberry_django.type(
|
@strawberry_django.type(
|
||||||
models.WirelessLink,
|
models.WirelessLink,
|
||||||
|
@ -0,0 +1,77 @@
|
|||||||
|
# Generated by Django 5.0.9 on 2024-11-04 16:00
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('contenttypes', '0002_remove_content_type_name'),
|
||||||
|
('dcim', '0196_qinq_svlan'),
|
||||||
|
('wireless', '0010_charfield_null_choices'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='wirelesslan',
|
||||||
|
name='_location',
|
||||||
|
field=models.ForeignKey(
|
||||||
|
blank=True,
|
||||||
|
null=True,
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
related_name='_%(class)ss',
|
||||||
|
to='dcim.location',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='wirelesslan',
|
||||||
|
name='_region',
|
||||||
|
field=models.ForeignKey(
|
||||||
|
blank=True,
|
||||||
|
null=True,
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
related_name='_%(class)ss',
|
||||||
|
to='dcim.region',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='wirelesslan',
|
||||||
|
name='_site',
|
||||||
|
field=models.ForeignKey(
|
||||||
|
blank=True,
|
||||||
|
null=True,
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
related_name='_%(class)ss',
|
||||||
|
to='dcim.site',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='wirelesslan',
|
||||||
|
name='_site_group',
|
||||||
|
field=models.ForeignKey(
|
||||||
|
blank=True,
|
||||||
|
null=True,
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
related_name='_%(class)ss',
|
||||||
|
to='dcim.sitegroup',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='wirelesslan',
|
||||||
|
name='scope_id',
|
||||||
|
field=models.PositiveBigIntegerField(blank=True, null=True),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='wirelesslan',
|
||||||
|
name='scope_type',
|
||||||
|
field=models.ForeignKey(
|
||||||
|
blank=True,
|
||||||
|
limit_choices_to=models.Q(('model__in', ('region', 'sitegroup', 'site', 'location'))),
|
||||||
|
null=True,
|
||||||
|
on_delete=django.db.models.deletion.PROTECT,
|
||||||
|
related_name='+',
|
||||||
|
to='contenttypes.contenttype',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
@ -4,6 +4,7 @@ from django.utils.translation import gettext_lazy as _
|
|||||||
|
|
||||||
from dcim.choices import LinkStatusChoices
|
from dcim.choices import LinkStatusChoices
|
||||||
from dcim.constants import WIRELESS_IFACE_TYPES
|
from dcim.constants import WIRELESS_IFACE_TYPES
|
||||||
|
from dcim.models.mixins import CachedScopeMixin
|
||||||
from netbox.models import NestedGroupModel, PrimaryModel
|
from netbox.models import NestedGroupModel, PrimaryModel
|
||||||
from netbox.models.mixins import DistanceMixin
|
from netbox.models.mixins import DistanceMixin
|
||||||
from .choices import *
|
from .choices import *
|
||||||
@ -71,7 +72,7 @@ class WirelessLANGroup(NestedGroupModel):
|
|||||||
verbose_name_plural = _('wireless LAN groups')
|
verbose_name_plural = _('wireless LAN groups')
|
||||||
|
|
||||||
|
|
||||||
class WirelessLAN(WirelessAuthenticationBase, PrimaryModel):
|
class WirelessLAN(WirelessAuthenticationBase, CachedScopeMixin, PrimaryModel):
|
||||||
"""
|
"""
|
||||||
A wireless network formed among an arbitrary number of access point and clients.
|
A wireless network formed among an arbitrary number of access point and clients.
|
||||||
"""
|
"""
|
||||||
@ -107,7 +108,7 @@ class WirelessLAN(WirelessAuthenticationBase, PrimaryModel):
|
|||||||
null=True
|
null=True
|
||||||
)
|
)
|
||||||
|
|
||||||
clone_fields = ('ssid', 'group', 'tenant', 'description')
|
clone_fields = ('ssid', 'group', 'scope_type', 'scope_id', 'tenant', 'description')
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
ordering = ('ssid', 'pk')
|
ordering = ('ssid', 'pk')
|
||||||
|
@ -51,6 +51,13 @@ class WirelessLANTable(TenancyColumnsMixin, NetBoxTable):
|
|||||||
status = columns.ChoiceFieldColumn(
|
status = columns.ChoiceFieldColumn(
|
||||||
verbose_name=_('Status'),
|
verbose_name=_('Status'),
|
||||||
)
|
)
|
||||||
|
scope_type = columns.ContentTypeColumn(
|
||||||
|
verbose_name=_('Scope Type'),
|
||||||
|
)
|
||||||
|
scope = tables.Column(
|
||||||
|
verbose_name=_('Scope'),
|
||||||
|
linkify=True
|
||||||
|
)
|
||||||
interface_count = tables.Column(
|
interface_count = tables.Column(
|
||||||
verbose_name=_('Interfaces')
|
verbose_name=_('Interfaces')
|
||||||
)
|
)
|
||||||
@ -65,7 +72,7 @@ class WirelessLANTable(TenancyColumnsMixin, NetBoxTable):
|
|||||||
model = WirelessLAN
|
model = WirelessLAN
|
||||||
fields = (
|
fields = (
|
||||||
'pk', 'ssid', 'group', 'status', 'tenant', 'tenant_group', 'vlan', 'interface_count', 'auth_type',
|
'pk', 'ssid', 'group', 'status', 'tenant', 'tenant_group', 'vlan', 'interface_count', 'auth_type',
|
||||||
'auth_cipher', 'auth_psk', 'description', 'comments', 'tags', 'created', 'last_updated',
|
'auth_cipher', 'auth_psk', 'scope', 'scope_type', 'description', 'comments', 'tags', 'created', 'last_updated',
|
||||||
)
|
)
|
||||||
default_columns = ('pk', 'ssid', 'group', 'status', 'description', 'vlan', 'auth_type', 'interface_count')
|
default_columns = ('pk', 'ssid', 'group', 'status', 'description', 'vlan', 'auth_type', 'interface_count')
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
|
|
||||||
from dcim.choices import InterfaceTypeChoices
|
from dcim.choices import InterfaceTypeChoices
|
||||||
from dcim.models import Interface
|
from dcim.models import Interface, Site
|
||||||
from tenancy.models import Tenant
|
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.choices import *
|
||||||
@ -53,6 +53,12 @@ class WirelessLANTest(APIViewTestCases.APIViewTestCase):
|
|||||||
@classmethod
|
@classmethod
|
||||||
def setUpTestData(cls):
|
def setUpTestData(cls):
|
||||||
|
|
||||||
|
sites = (
|
||||||
|
Site(name='Site 1', slug='site-1'),
|
||||||
|
Site(name='Site 2', slug='site-2'),
|
||||||
|
)
|
||||||
|
Site.objects.bulk_create(sites)
|
||||||
|
|
||||||
tenants = (
|
tenants = (
|
||||||
Tenant(name='Tenant 1', slug='tenant-1'),
|
Tenant(name='Tenant 1', slug='tenant-1'),
|
||||||
Tenant(name='Tenant 2', slug='tenant-2'),
|
Tenant(name='Tenant 2', slug='tenant-2'),
|
||||||
@ -94,6 +100,8 @@ class WirelessLANTest(APIViewTestCases.APIViewTestCase):
|
|||||||
'status': WirelessLANStatusChoices.STATUS_DISABLED,
|
'status': WirelessLANStatusChoices.STATUS_DISABLED,
|
||||||
'tenant': tenants[0].pk,
|
'tenant': tenants[0].pk,
|
||||||
'auth_type': WirelessAuthTypeChoices.TYPE_WPA_ENTERPRISE,
|
'auth_type': WirelessAuthTypeChoices.TYPE_WPA_ENTERPRISE,
|
||||||
|
'scope_type': 'dcim.site',
|
||||||
|
'scope_id': sites[1].pk,
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
from django.test import TestCase
|
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, Location, Region, Site, SiteGroup
|
||||||
from ipam.models import VLAN
|
from ipam.models import VLAN
|
||||||
from netbox.choices import DistanceUnitChoices
|
from netbox.choices import DistanceUnitChoices
|
||||||
from tenancy.models import Tenant
|
from tenancy.models import Tenant
|
||||||
@ -110,6 +110,36 @@ class WirelessLANTestCase(TestCase, ChangeLoggedFilterSetTests):
|
|||||||
)
|
)
|
||||||
VLAN.objects.bulk_create(vlans)
|
VLAN.objects.bulk_create(vlans)
|
||||||
|
|
||||||
|
regions = (
|
||||||
|
Region(name='Test Region 1', slug='test-region-1'),
|
||||||
|
Region(name='Test Region 2', slug='test-region-2'),
|
||||||
|
Region(name='Test Region 3', slug='test-region-3'),
|
||||||
|
)
|
||||||
|
for r in regions:
|
||||||
|
r.save()
|
||||||
|
|
||||||
|
site_groups = (
|
||||||
|
SiteGroup(name='Site Group 1', slug='site-group-1'),
|
||||||
|
SiteGroup(name='Site Group 2', slug='site-group-2'),
|
||||||
|
SiteGroup(name='Site Group 3', slug='site-group-3'),
|
||||||
|
)
|
||||||
|
for site_group in site_groups:
|
||||||
|
site_group.save()
|
||||||
|
|
||||||
|
sites = (
|
||||||
|
Site(name='Test Site 1', slug='test-site-1', region=regions[0], group=site_groups[0]),
|
||||||
|
Site(name='Test Site 2', slug='test-site-2', region=regions[1], group=site_groups[1]),
|
||||||
|
Site(name='Test Site 3', slug='test-site-3', region=regions[2], group=site_groups[2]),
|
||||||
|
)
|
||||||
|
Site.objects.bulk_create(sites)
|
||||||
|
|
||||||
|
locations = (
|
||||||
|
Location(name='Location 1', slug='location-1', site=sites[0]),
|
||||||
|
Location(name='Location 2', slug='location-2', site=sites[2]),
|
||||||
|
)
|
||||||
|
for location in locations:
|
||||||
|
location.save()
|
||||||
|
|
||||||
tenants = (
|
tenants = (
|
||||||
Tenant(name='Tenant 1', slug='tenant-1'),
|
Tenant(name='Tenant 1', slug='tenant-1'),
|
||||||
Tenant(name='Tenant 2', slug='tenant-2'),
|
Tenant(name='Tenant 2', slug='tenant-2'),
|
||||||
@ -127,7 +157,8 @@ class WirelessLANTestCase(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',
|
||||||
description='foobar1'
|
description='foobar1',
|
||||||
|
scope=sites[0]
|
||||||
),
|
),
|
||||||
WirelessLAN(
|
WirelessLAN(
|
||||||
ssid='WLAN2',
|
ssid='WLAN2',
|
||||||
@ -138,7 +169,8 @@ class WirelessLANTestCase(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',
|
||||||
description='foobar2'
|
description='foobar2',
|
||||||
|
scope=locations[0]
|
||||||
),
|
),
|
||||||
WirelessLAN(
|
WirelessLAN(
|
||||||
ssid='WLAN3',
|
ssid='WLAN3',
|
||||||
@ -149,12 +181,14 @@ class WirelessLANTestCase(TestCase, ChangeLoggedFilterSetTests):
|
|||||||
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',
|
||||||
description='foobar3'
|
description='foobar3',
|
||||||
|
scope=locations[1]
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
WirelessLAN.objects.bulk_create(wireless_lans)
|
for wireless_lan in wireless_lans:
|
||||||
|
wireless_lan.save()
|
||||||
|
|
||||||
device = create_test_device('Device 1')
|
device = create_test_device('Device 1', site=sites[0])
|
||||||
interfaces = (
|
interfaces = (
|
||||||
Interface(device=device, name='Interface 1', type=InterfaceTypeChoices.TYPE_80211N),
|
Interface(device=device, name='Interface 1', type=InterfaceTypeChoices.TYPE_80211N),
|
||||||
Interface(device=device, name='Interface 2', type=InterfaceTypeChoices.TYPE_80211N),
|
Interface(device=device, name='Interface 2', type=InterfaceTypeChoices.TYPE_80211N),
|
||||||
@ -217,6 +251,38 @@ class WirelessLANTestCase(TestCase, ChangeLoggedFilterSetTests):
|
|||||||
params = {'interface_id': [interfaces[0].pk, interfaces[1].pk]}
|
params = {'interface_id': [interfaces[0].pk, interfaces[1].pk]}
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
|
|
||||||
|
def test_region(self):
|
||||||
|
regions = Region.objects.all()[:2]
|
||||||
|
params = {'region_id': [regions[0].pk, regions[1].pk]}
|
||||||
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
|
params = {'region': [regions[0].slug, regions[1].slug]}
|
||||||
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
|
|
||||||
|
def test_site_group(self):
|
||||||
|
site_groups = SiteGroup.objects.all()[:2]
|
||||||
|
params = {'site_group_id': [site_groups[0].pk, site_groups[1].pk]}
|
||||||
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
|
params = {'site_group': [site_groups[0].slug, site_groups[1].slug]}
|
||||||
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
|
|
||||||
|
def test_site(self):
|
||||||
|
sites = Site.objects.all()[:2]
|
||||||
|
params = {'site_id': [sites[0].pk, sites[1].pk]}
|
||||||
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
|
params = {'site': [sites[0].slug, sites[1].slug]}
|
||||||
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
|
|
||||||
|
def test_location(self):
|
||||||
|
locations = Location.objects.all()[:1]
|
||||||
|
params = {'location_id': [locations[0].pk,]}
|
||||||
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
|
||||||
|
params = {'location': [locations[0].slug,]}
|
||||||
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
|
||||||
|
|
||||||
|
def test_scope_type(self):
|
||||||
|
params = {'scope_type': 'dcim.location'}
|
||||||
|
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()
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
|
from django.contrib.contenttypes.models import ContentType
|
||||||
from wireless.choices import *
|
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, Site
|
||||||
from netbox.choices import DistanceUnitChoices
|
from netbox.choices import DistanceUnitChoices
|
||||||
from tenancy.models import Tenant
|
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
|
||||||
@ -56,6 +57,12 @@ class WirelessLANTestCase(ViewTestCases.PrimaryObjectViewTestCase):
|
|||||||
@classmethod
|
@classmethod
|
||||||
def setUpTestData(cls):
|
def setUpTestData(cls):
|
||||||
|
|
||||||
|
sites = (
|
||||||
|
Site(name='Site 1', slug='site-1'),
|
||||||
|
Site(name='Site 2', slug='site-2'),
|
||||||
|
)
|
||||||
|
Site.objects.bulk_create(sites)
|
||||||
|
|
||||||
tenants = (
|
tenants = (
|
||||||
Tenant(name='Tenant 1', slug='tenant-1'),
|
Tenant(name='Tenant 1', slug='tenant-1'),
|
||||||
Tenant(name='Tenant 2', slug='tenant-2'),
|
Tenant(name='Tenant 2', slug='tenant-2'),
|
||||||
@ -98,15 +105,17 @@ class WirelessLANTestCase(ViewTestCases.PrimaryObjectViewTestCase):
|
|||||||
'ssid': 'WLAN2',
|
'ssid': 'WLAN2',
|
||||||
'group': groups[1].pk,
|
'group': groups[1].pk,
|
||||||
'status': WirelessLANStatusChoices.STATUS_DISABLED,
|
'status': WirelessLANStatusChoices.STATUS_DISABLED,
|
||||||
|
'scope_type': ContentType.objects.get_for_model(Site).pk,
|
||||||
|
'scope': sites[1].pk,
|
||||||
'tenant': tenants[1].pk,
|
'tenant': tenants[1].pk,
|
||||||
'tags': [t.pk for t in tags],
|
'tags': [t.pk for t in tags],
|
||||||
}
|
}
|
||||||
|
|
||||||
cls.csv_data = (
|
cls.csv_data = (
|
||||||
"group,ssid,status,tenant",
|
"group,ssid,status,tenant,scope_type,scope_id",
|
||||||
f"Wireless LAN Group 2,WLAN4,{WirelessLANStatusChoices.STATUS_ACTIVE},{tenants[0].name}",
|
f"Wireless LAN Group 2,WLAN4,{WirelessLANStatusChoices.STATUS_ACTIVE},{tenants[0].name},,",
|
||||||
f"Wireless LAN Group 2,WLAN5,{WirelessLANStatusChoices.STATUS_DISABLED},{tenants[1].name}",
|
f"Wireless LAN Group 2,WLAN5,{WirelessLANStatusChoices.STATUS_DISABLED},{tenants[1].name},dcim.site,{sites[0].pk}",
|
||||||
f"Wireless LAN Group 2,WLAN6,{WirelessLANStatusChoices.STATUS_RESERVED},{tenants[2].name}",
|
f"Wireless LAN Group 2,WLAN6,{WirelessLANStatusChoices.STATUS_RESERVED},{tenants[2].name},dcim.site,{sites[1].pk}",
|
||||||
)
|
)
|
||||||
|
|
||||||
cls.csv_update_data = (
|
cls.csv_update_data = (
|
||||||
|
Loading…
Reference in New Issue
Block a user