mirror of
https://github.com/netbox-community/netbox.git
synced 2025-07-24 17:38:37 -06:00
Add WirelessLink model
This commit is contained in:
parent
5271680483
commit
90e9f34494
@ -1,10 +1,11 @@
|
|||||||
|
# Generated by Django 3.2.8 on 2021-10-13 13:44
|
||||||
|
|
||||||
from django.db import migrations, models
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
('wireless', '0001_initial'),
|
|
||||||
('dcim', '0135_location_tenant'),
|
('dcim', '0135_location_tenant'),
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -19,9 +20,4 @@ class Migration(migrations.Migration):
|
|||||||
name='rf_channel_width',
|
name='rf_channel_width',
|
||||||
field=models.PositiveSmallIntegerField(blank=True, null=True),
|
field=models.PositiveSmallIntegerField(blank=True, null=True),
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
|
||||||
model_name='interface',
|
|
||||||
name='wireless_lans',
|
|
||||||
field=models.ManyToManyField(blank=True, related_name='interfaces', to='wireless.WirelessLAN'),
|
|
||||||
),
|
|
||||||
]
|
]
|
||||||
|
19
netbox/dcim/migrations/0137_wireless.py
Normal file
19
netbox/dcim/migrations/0137_wireless.py
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
# Generated by Django 3.2.8 on 2021-10-13 13:44
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('dcim', '0136_wireless'),
|
||||||
|
('wireless', '0001_wireless'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='interface',
|
||||||
|
name='wireless_lans',
|
||||||
|
field=models.ManyToManyField(blank=True, related_name='interfaces', to='wireless.WirelessLAN'),
|
||||||
|
),
|
||||||
|
]
|
@ -196,6 +196,7 @@ WIRELESS_MENU = Menu(
|
|||||||
label='Wireless',
|
label='Wireless',
|
||||||
items=(
|
items=(
|
||||||
get_model_item('wireless', 'wirelesslan', 'Wireless LANs'),
|
get_model_item('wireless', 'wirelesslan', 'Wireless LANs'),
|
||||||
|
get_model_item('wireless', 'wirelesslink', 'Wirelesss Links', actions=['import']),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
20
netbox/templates/wireless/inc/wirelesslink_interface.html
Normal file
20
netbox/templates/wireless/inc/wirelesslink_interface.html
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
<table class="table table-hover panel-body attr-table">
|
||||||
|
<tr>
|
||||||
|
<td>Device</td>
|
||||||
|
<td>
|
||||||
|
<a href="{{ interface.device.get_absolute_url }}">{{ interface.device }}</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Interface</td>
|
||||||
|
<td>
|
||||||
|
<a href="{{ interface.get_absolute_url }}">{{ interface }}</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Type</td>
|
||||||
|
<td>
|
||||||
|
{{ interface.get_type_display }}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
@ -30,7 +30,7 @@
|
|||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% include 'extras/inc/tags_panel.html' with tags=object.tags.all url='dcim:site_list' %}
|
{% include 'extras/inc/tags_panel.html' with tags=object.tags.all url='wireless:wirelesslan_list' %}
|
||||||
{% plugin_left_page object %}
|
{% plugin_left_page object %}
|
||||||
</div>
|
</div>
|
||||||
<div class="col col-md-6">
|
<div class="col col-md-6">
|
||||||
|
48
netbox/templates/wireless/wirelesslink.html
Normal file
48
netbox/templates/wireless/wirelesslink.html
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
{% extends 'generic/object.html' %}
|
||||||
|
{% load helpers %}
|
||||||
|
{% load plugins %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="row">
|
||||||
|
<div class="col col-md-6">
|
||||||
|
<div class="card">
|
||||||
|
<h5 class="card-header">Interface A</h5>
|
||||||
|
<div class="card-body">
|
||||||
|
{% include 'wireless/inc/wirelesslink_interface.html' with interface=object.interface_a %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card">
|
||||||
|
<h5 class="card-header">Link Properties</h5>
|
||||||
|
<div class="card-body">
|
||||||
|
<table class="table table-hover attr-table">
|
||||||
|
<tr>
|
||||||
|
<th scope="row">SSID</th>
|
||||||
|
<td>{{ object.ssid|placeholder }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th scope="row">Description</th>
|
||||||
|
<td>{{ object.description|placeholder }}</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% plugin_left_page object %}
|
||||||
|
</div>
|
||||||
|
<div class="col col-md-6">
|
||||||
|
<div class="card">
|
||||||
|
<h5 class="card-header">Interface B</h5>
|
||||||
|
<div class="card-body">
|
||||||
|
{% include 'wireless/inc/wirelesslink_interface.html' with interface=object.interface_b %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% include 'inc/custom_fields_panel.html' %}
|
||||||
|
{% include 'extras/inc/tags_panel.html' with tags=object.tags.all url='wireless:wirelesslink_list' %}
|
||||||
|
{% plugin_right_page object %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col col-md-12">
|
||||||
|
{% plugin_full_width_page object %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
@ -5,12 +5,21 @@ from wireless.models import *
|
|||||||
|
|
||||||
__all__ = (
|
__all__ = (
|
||||||
'NestedWirelessLANSerializer',
|
'NestedWirelessLANSerializer',
|
||||||
|
'NestedWirelessLinkSerializer',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class NestedWirelessLANSerializer(WritableNestedSerializer):
|
class NestedWirelessLANSerializer(WritableNestedSerializer):
|
||||||
url = serializers.HyperlinkedIdentityField(view_name='wireless-api:ssid-detail')
|
url = serializers.HyperlinkedIdentityField(view_name='wireless-api:wirelesslan-detail')
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = WirelessLAN
|
model = WirelessLAN
|
||||||
fields = ['id', 'url', 'display', 'ssid']
|
fields = ['id', 'url', 'display', 'ssid']
|
||||||
|
|
||||||
|
|
||||||
|
class NestedWirelessLinkSerializer(WritableNestedSerializer):
|
||||||
|
url = serializers.HyperlinkedIdentityField(view_name='wireless-api:wirelesslink-detail')
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = WirelessLink
|
||||||
|
fields = ['id', 'url', 'display', 'ssid']
|
||||||
|
@ -1,16 +1,19 @@
|
|||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
|
||||||
|
from dcim.api.serializers import NestedInterfaceSerializer
|
||||||
from ipam.api.serializers import NestedVLANSerializer
|
from ipam.api.serializers import NestedVLANSerializer
|
||||||
from netbox.api.serializers import PrimaryModelSerializer
|
from netbox.api.serializers import PrimaryModelSerializer
|
||||||
from wireless.models import *
|
from wireless.models import *
|
||||||
|
from .nested_serializers import *
|
||||||
|
|
||||||
__all__ = (
|
__all__ = (
|
||||||
'WirelessLANSerializer',
|
'WirelessLANSerializer',
|
||||||
|
'WirelessLinkSerializer',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class WirelessLANSerializer(PrimaryModelSerializer):
|
class WirelessLANSerializer(PrimaryModelSerializer):
|
||||||
url = serializers.HyperlinkedIdentityField(view_name='wireless-api:ssid-detail')
|
url = serializers.HyperlinkedIdentityField(view_name='wireless-api:wirelesslan-detail')
|
||||||
vlan = NestedVLANSerializer(required=False, allow_null=True)
|
vlan = NestedVLANSerializer(required=False, allow_null=True)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
@ -18,3 +21,15 @@ class WirelessLANSerializer(PrimaryModelSerializer):
|
|||||||
fields = [
|
fields = [
|
||||||
'id', 'url', 'display', 'ssid', 'description', 'vlan',
|
'id', 'url', 'display', 'ssid', 'description', 'vlan',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class WirelessLinkSerializer(PrimaryModelSerializer):
|
||||||
|
url = serializers.HyperlinkedIdentityField(view_name='wireless-api:wirelesslink-detail')
|
||||||
|
interface_a = NestedInterfaceSerializer()
|
||||||
|
interface_b = NestedInterfaceSerializer()
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = WirelessLAN
|
||||||
|
fields = [
|
||||||
|
'id', 'url', 'display', 'interface_a', 'interface_b', 'ssid', 'description',
|
||||||
|
]
|
||||||
|
@ -6,6 +6,7 @@ router = OrderedDefaultRouter()
|
|||||||
router.APIRootView = views.WirelessRootView
|
router.APIRootView = views.WirelessRootView
|
||||||
|
|
||||||
router.register('wireless-lans', views.WirelessLANViewSet)
|
router.register('wireless-lans', views.WirelessLANViewSet)
|
||||||
|
router.register('wireless-links', views.WirelessLinkViewSet)
|
||||||
|
|
||||||
app_name = 'wireless-api'
|
app_name = 'wireless-api'
|
||||||
urlpatterns = router.urls
|
urlpatterns = router.urls
|
||||||
|
@ -14,11 +14,13 @@ class WirelessRootView(APIRootView):
|
|||||||
return 'Wireless'
|
return 'Wireless'
|
||||||
|
|
||||||
|
|
||||||
#
|
|
||||||
# Providers
|
|
||||||
#
|
|
||||||
|
|
||||||
class WirelessLANViewSet(CustomFieldModelViewSet):
|
class WirelessLANViewSet(CustomFieldModelViewSet):
|
||||||
queryset = WirelessLAN.objects.prefetch_related('tags')
|
queryset = WirelessLAN.objects.prefetch_related('vlan', 'tags')
|
||||||
serializer_class = serializers.WirelessLANSerializer
|
serializer_class = serializers.WirelessLANSerializer
|
||||||
filterset_class = filtersets.WirelessLANFilterSet
|
filterset_class = filtersets.WirelessLANFilterSet
|
||||||
|
|
||||||
|
|
||||||
|
class WirelessLinkViewSet(CustomFieldModelViewSet):
|
||||||
|
queryset = WirelessLink.objects.prefetch_related('interface_a', 'interface_b', 'tags')
|
||||||
|
serializer_class = serializers.WirelessLinkSerializer
|
||||||
|
filterset_class = filtersets.WirelessLinkFilterSet
|
||||||
|
@ -7,6 +7,7 @@ from .models import *
|
|||||||
|
|
||||||
__all__ = (
|
__all__ = (
|
||||||
'WirelessLANFilterSet',
|
'WirelessLANFilterSet',
|
||||||
|
'WirelessLinkFilterSet',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -29,3 +30,24 @@ class WirelessLANFilterSet(PrimaryModelFilterSet):
|
|||||||
Q(description__icontains=value)
|
Q(description__icontains=value)
|
||||||
)
|
)
|
||||||
return queryset.filter(qs_filter)
|
return queryset.filter(qs_filter)
|
||||||
|
|
||||||
|
|
||||||
|
class WirelessLinkFilterSet(PrimaryModelFilterSet):
|
||||||
|
q = django_filters.CharFilter(
|
||||||
|
method='search',
|
||||||
|
label='Search',
|
||||||
|
)
|
||||||
|
tag = TagFilter()
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = WirelessLink
|
||||||
|
fields = ['id', 'ssid']
|
||||||
|
|
||||||
|
def search(self, queryset, name, value):
|
||||||
|
if not value.strip():
|
||||||
|
return queryset
|
||||||
|
qs_filter = (
|
||||||
|
Q(ssid__icontains=value) |
|
||||||
|
Q(description__icontains=value)
|
||||||
|
)
|
||||||
|
return queryset.filter(qs_filter)
|
||||||
|
@ -4,9 +4,11 @@ from dcim.models import *
|
|||||||
from extras.forms import AddRemoveTagsForm, CustomFieldModelBulkEditForm
|
from extras.forms import AddRemoveTagsForm, CustomFieldModelBulkEditForm
|
||||||
from ipam.models import VLAN
|
from ipam.models import VLAN
|
||||||
from utilities.forms import BootstrapMixin, DynamicModelChoiceField
|
from utilities.forms import BootstrapMixin, DynamicModelChoiceField
|
||||||
|
from wireless.constants import SSID_MAX_LENGTH
|
||||||
|
|
||||||
__all__ = (
|
__all__ = (
|
||||||
'WirelessLANBulkEditForm',
|
'WirelessLANBulkEditForm',
|
||||||
|
'WirelessLinkBulkEditForm',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -19,11 +21,30 @@ class WirelessLANBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldMode
|
|||||||
queryset=VLAN.objects.all(),
|
queryset=VLAN.objects.all(),
|
||||||
required=False,
|
required=False,
|
||||||
)
|
)
|
||||||
|
ssid = forms.CharField(
|
||||||
|
max_length=SSID_MAX_LENGTH,
|
||||||
|
required=False
|
||||||
|
)
|
||||||
description = forms.CharField(
|
description = forms.CharField(
|
||||||
required=False
|
required=False
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
nullable_fields = [
|
nullable_fields = ['vlan', 'ssid', 'description']
|
||||||
'vlan', 'description',
|
|
||||||
]
|
|
||||||
|
class WirelessLinkBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldModelBulkEditForm):
|
||||||
|
pk = forms.ModelMultipleChoiceField(
|
||||||
|
queryset=PowerFeed.objects.all(),
|
||||||
|
widget=forms.MultipleHiddenInput
|
||||||
|
)
|
||||||
|
ssid = forms.CharField(
|
||||||
|
max_length=SSID_MAX_LENGTH,
|
||||||
|
required=False
|
||||||
|
)
|
||||||
|
description = forms.CharField(
|
||||||
|
required=False
|
||||||
|
)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
nullable_fields = ['ssid', 'description']
|
||||||
|
@ -1,10 +1,12 @@
|
|||||||
|
from dcim.models import Interface
|
||||||
from extras.forms import CustomFieldModelCSVForm
|
from extras.forms import CustomFieldModelCSVForm
|
||||||
from ipam.models import VLAN
|
from ipam.models import VLAN
|
||||||
from utilities.forms import CSVModelChoiceField
|
from utilities.forms import CSVModelChoiceField
|
||||||
from wireless.models import WirelessLAN
|
from wireless.models import *
|
||||||
|
|
||||||
__all__ = (
|
__all__ = (
|
||||||
'WirelessLANCSVForm',
|
'WirelessLANCSVForm',
|
||||||
|
'WirelessLinkCSVForm',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -18,3 +20,16 @@ class WirelessLANCSVForm(CustomFieldModelCSVForm):
|
|||||||
class Meta:
|
class Meta:
|
||||||
model = WirelessLAN
|
model = WirelessLAN
|
||||||
fields = ('ssid', 'description', 'vlan')
|
fields = ('ssid', 'description', 'vlan')
|
||||||
|
|
||||||
|
|
||||||
|
class WirelessLinkCSVForm(CustomFieldModelCSVForm):
|
||||||
|
interface_a = CSVModelChoiceField(
|
||||||
|
queryset=Interface.objects.all()
|
||||||
|
)
|
||||||
|
interface_b = CSVModelChoiceField(
|
||||||
|
queryset=Interface.objects.all()
|
||||||
|
)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = WirelessLink
|
||||||
|
fields = ('interface_a', 'interface_b', 'ssid', 'description')
|
||||||
|
@ -3,7 +3,12 @@ from django.utils.translation import gettext as _
|
|||||||
|
|
||||||
from extras.forms import CustomFieldModelFilterForm
|
from extras.forms import CustomFieldModelFilterForm
|
||||||
from utilities.forms import BootstrapMixin, TagFilterField
|
from utilities.forms import BootstrapMixin, TagFilterField
|
||||||
from .models import WirelessLAN
|
from wireless.models import *
|
||||||
|
|
||||||
|
__all__ = (
|
||||||
|
'WirelessLANFilterForm',
|
||||||
|
'WirelessLinkFilterForm',
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class WirelessLANFilterForm(BootstrapMixin, CustomFieldModelFilterForm):
|
class WirelessLANFilterForm(BootstrapMixin, CustomFieldModelFilterForm):
|
||||||
@ -16,4 +21,25 @@ class WirelessLANFilterForm(BootstrapMixin, CustomFieldModelFilterForm):
|
|||||||
widget=forms.TextInput(attrs={'placeholder': _('All Fields')}),
|
widget=forms.TextInput(attrs={'placeholder': _('All Fields')}),
|
||||||
label=_('Search')
|
label=_('Search')
|
||||||
)
|
)
|
||||||
|
ssid = forms.CharField(
|
||||||
|
required=False,
|
||||||
|
label='SSID'
|
||||||
|
)
|
||||||
|
tag = TagFilterField(model)
|
||||||
|
|
||||||
|
|
||||||
|
class WirelessLinkFilterForm(BootstrapMixin, CustomFieldModelFilterForm):
|
||||||
|
model = WirelessLink
|
||||||
|
field_groups = [
|
||||||
|
['q', 'tag'],
|
||||||
|
]
|
||||||
|
q = forms.CharField(
|
||||||
|
required=False,
|
||||||
|
widget=forms.TextInput(attrs={'placeholder': _('All Fields')}),
|
||||||
|
label=_('Search')
|
||||||
|
)
|
||||||
|
ssid = forms.CharField(
|
||||||
|
required=False,
|
||||||
|
label='SSID'
|
||||||
|
)
|
||||||
tag = TagFilterField(model)
|
tag = TagFilterField(model)
|
||||||
|
@ -1,11 +1,13 @@
|
|||||||
|
from dcim.models import Interface
|
||||||
from extras.forms import CustomFieldModelForm
|
from extras.forms import CustomFieldModelForm
|
||||||
from extras.models import Tag
|
from extras.models import Tag
|
||||||
from ipam.models import VLAN
|
from ipam.models import VLAN
|
||||||
from utilities.forms import BootstrapMixin, DynamicModelChoiceField, DynamicModelMultipleChoiceField
|
from utilities.forms import BootstrapMixin, DynamicModelChoiceField, DynamicModelMultipleChoiceField
|
||||||
from wireless.models import WirelessLAN
|
from wireless.models import *
|
||||||
|
|
||||||
__all__ = (
|
__all__ = (
|
||||||
'WirelessLANForm',
|
'WirelessLANForm',
|
||||||
|
'WirelessLinkForm',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -28,3 +30,28 @@ class WirelessLANForm(BootstrapMixin, CustomFieldModelForm):
|
|||||||
('Wireless LAN', ('ssid', 'description', 'tags')),
|
('Wireless LAN', ('ssid', 'description', 'tags')),
|
||||||
('VLAN', ('vlan',)),
|
('VLAN', ('vlan',)),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class WirelessLinkForm(BootstrapMixin, CustomFieldModelForm):
|
||||||
|
interface_a = DynamicModelChoiceField(
|
||||||
|
queryset=Interface.objects.all(),
|
||||||
|
query_params={
|
||||||
|
'kind': 'wireless'
|
||||||
|
}
|
||||||
|
)
|
||||||
|
interface_b = DynamicModelChoiceField(
|
||||||
|
queryset=Interface.objects.all(),
|
||||||
|
query_params={
|
||||||
|
'kind': 'wireless'
|
||||||
|
}
|
||||||
|
)
|
||||||
|
tags = DynamicModelMultipleChoiceField(
|
||||||
|
queryset=Tag.objects.all(),
|
||||||
|
required=False
|
||||||
|
)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = WirelessLink
|
||||||
|
fields = [
|
||||||
|
'interface_a', 'interface_b', 'ssid', 'description', 'tags',
|
||||||
|
]
|
||||||
|
@ -1,34 +0,0 @@
|
|||||||
import django.core.serializers.json
|
|
||||||
from django.db import migrations, models
|
|
||||||
import django.db.models.deletion
|
|
||||||
import taggit.managers
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
initial = True
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('ipam', '0050_iprange'),
|
|
||||||
('extras', '0062_clear_secrets_changelog'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='WirelessLAN',
|
|
||||||
fields=[
|
|
||||||
('created', models.DateField(auto_now_add=True, null=True)),
|
|
||||||
('last_updated', models.DateTimeField(auto_now=True, null=True)),
|
|
||||||
('custom_field_data', models.JSONField(blank=True, default=dict, encoder=django.core.serializers.json.DjangoJSONEncoder)),
|
|
||||||
('id', models.BigAutoField(primary_key=True, serialize=False)),
|
|
||||||
('ssid', models.CharField(max_length=32)),
|
|
||||||
('description', models.CharField(blank=True, max_length=200)),
|
|
||||||
('tags', taggit.managers.TaggableManager(through='extras.TaggedItem', to='extras.Tag')),
|
|
||||||
('vlan', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='ipam.vlan')),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'verbose_name': 'Wireless LAN',
|
|
||||||
'ordering': ('ssid', 'pk'),
|
|
||||||
},
|
|
||||||
),
|
|
||||||
]
|
|
57
netbox/wireless/migrations/0001_wireless.py
Normal file
57
netbox/wireless/migrations/0001_wireless.py
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
# Generated by Django 3.2.8 on 2021-10-13 13:44
|
||||||
|
|
||||||
|
import django.core.serializers.json
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
import taggit.managers
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
initial = True
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('dcim', '0136_wireless'),
|
||||||
|
('extras', '0062_clear_secrets_changelog'),
|
||||||
|
('ipam', '0050_iprange'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='WirelessLAN',
|
||||||
|
fields=[
|
||||||
|
('created', models.DateField(auto_now_add=True, null=True)),
|
||||||
|
('last_updated', models.DateTimeField(auto_now=True, null=True)),
|
||||||
|
('custom_field_data', models.JSONField(blank=True, default=dict, encoder=django.core.serializers.json.DjangoJSONEncoder)),
|
||||||
|
('id', models.BigAutoField(primary_key=True, serialize=False)),
|
||||||
|
('ssid', models.CharField(max_length=32)),
|
||||||
|
('description', models.CharField(blank=True, max_length=200)),
|
||||||
|
('tags', taggit.managers.TaggableManager(through='extras.TaggedItem', to='extras.Tag')),
|
||||||
|
('vlan', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='ipam.vlan')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'Wireless LAN',
|
||||||
|
'ordering': ('ssid', 'pk'),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='WirelessLink',
|
||||||
|
fields=[
|
||||||
|
('created', models.DateField(auto_now_add=True, null=True)),
|
||||||
|
('last_updated', models.DateTimeField(auto_now=True, null=True)),
|
||||||
|
('custom_field_data', models.JSONField(blank=True, default=dict, encoder=django.core.serializers.json.DjangoJSONEncoder)),
|
||||||
|
('id', models.BigAutoField(primary_key=True, serialize=False)),
|
||||||
|
('ssid', models.CharField(blank=True, max_length=32)),
|
||||||
|
('description', models.CharField(blank=True, max_length=200)),
|
||||||
|
('_interface_a_device', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='+', to='dcim.device')),
|
||||||
|
('_interface_b_device', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='+', to='dcim.device')),
|
||||||
|
('interface_a', models.ForeignKey(limit_choices_to={'type__in': ['ieee802.11a', 'ieee802.11g', 'ieee802.11n', 'ieee802.11ac', 'ieee802.11ad', 'ieee802.11ax']}, on_delete=django.db.models.deletion.PROTECT, related_name='+', to='dcim.interface')),
|
||||||
|
('interface_b', models.ForeignKey(limit_choices_to={'type__in': ['ieee802.11a', 'ieee802.11g', 'ieee802.11n', 'ieee802.11ac', 'ieee802.11ad', 'ieee802.11ax']}, on_delete=django.db.models.deletion.PROTECT, related_name='+', to='dcim.interface')),
|
||||||
|
('tags', taggit.managers.TaggableManager(through='extras.TaggedItem', to='extras.Tag')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'ordering': ['pk'],
|
||||||
|
'unique_together': {('interface_a', 'interface_b')},
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
@ -6,19 +6,22 @@ from dcim.constants import WIRELESS_IFACE_TYPES
|
|||||||
from extras.utils import extras_features
|
from extras.utils import extras_features
|
||||||
from netbox.models import BigIDModel, PrimaryModel
|
from netbox.models import BigIDModel, PrimaryModel
|
||||||
from utilities.querysets import RestrictedQuerySet
|
from utilities.querysets import RestrictedQuerySet
|
||||||
|
from .constants import SSID_MAX_LENGTH
|
||||||
|
|
||||||
__all__ = (
|
__all__ = (
|
||||||
'WirelessLAN',
|
'WirelessLAN',
|
||||||
|
'WirelessLink',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@extras_features('custom_fields', 'custom_links', 'export_templates', 'tags', 'webhooks')
|
@extras_features('custom_fields', 'custom_links', 'export_templates', 'tags', 'webhooks')
|
||||||
class WirelessLAN(PrimaryModel):
|
class WirelessLAN(PrimaryModel):
|
||||||
"""
|
"""
|
||||||
A service set identifier belonging to a wireless network.
|
A wireless network formed among an arbitrary number of access point and clients.
|
||||||
"""
|
"""
|
||||||
ssid = models.CharField(
|
ssid = models.CharField(
|
||||||
max_length=32
|
max_length=SSID_MAX_LENGTH,
|
||||||
|
verbose_name='SSID'
|
||||||
)
|
)
|
||||||
vlan = models.ForeignKey(
|
vlan = models.ForeignKey(
|
||||||
to='ipam.VLAN',
|
to='ipam.VLAN',
|
||||||
@ -42,4 +45,78 @@ class WirelessLAN(PrimaryModel):
|
|||||||
return self.ssid
|
return self.ssid
|
||||||
|
|
||||||
def get_absolute_url(self):
|
def get_absolute_url(self):
|
||||||
return reverse('wireless:ssid', args=[self.pk])
|
return reverse('wireless:wirelesslan', args=[self.pk])
|
||||||
|
|
||||||
|
|
||||||
|
@extras_features('custom_fields', 'custom_links', 'export_templates', 'tags', 'webhooks')
|
||||||
|
class WirelessLink(PrimaryModel):
|
||||||
|
"""
|
||||||
|
A point-to-point connection between two wireless Interfaces.
|
||||||
|
"""
|
||||||
|
interface_a = models.ForeignKey(
|
||||||
|
to='dcim.Interface',
|
||||||
|
limit_choices_to={'type__in': WIRELESS_IFACE_TYPES},
|
||||||
|
on_delete=models.PROTECT,
|
||||||
|
related_name='+'
|
||||||
|
)
|
||||||
|
interface_b = models.ForeignKey(
|
||||||
|
to='dcim.Interface',
|
||||||
|
limit_choices_to={'type__in': WIRELESS_IFACE_TYPES},
|
||||||
|
on_delete=models.PROTECT,
|
||||||
|
related_name='+'
|
||||||
|
)
|
||||||
|
ssid = models.CharField(
|
||||||
|
max_length=SSID_MAX_LENGTH,
|
||||||
|
blank=True,
|
||||||
|
verbose_name='SSID'
|
||||||
|
)
|
||||||
|
description = models.CharField(
|
||||||
|
max_length=200,
|
||||||
|
blank=True
|
||||||
|
)
|
||||||
|
|
||||||
|
# Cache the associated device for the A and B interfaces. This enables filtering of WirelessLinks by their
|
||||||
|
# associated Devices.
|
||||||
|
_interface_a_device = models.ForeignKey(
|
||||||
|
to='dcim.Device',
|
||||||
|
on_delete=models.CASCADE,
|
||||||
|
related_name='+',
|
||||||
|
blank=True,
|
||||||
|
null=True
|
||||||
|
)
|
||||||
|
_interface_b_device = models.ForeignKey(
|
||||||
|
to='dcim.Device',
|
||||||
|
on_delete=models.CASCADE,
|
||||||
|
related_name='+',
|
||||||
|
blank=True,
|
||||||
|
null=True
|
||||||
|
)
|
||||||
|
|
||||||
|
objects = RestrictedQuerySet.as_manager()
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
ordering = ['pk']
|
||||||
|
unique_together = ('interface_a', 'interface_b')
|
||||||
|
|
||||||
|
def get_absolute_url(self):
|
||||||
|
return reverse('wireless:wirelesslink', args=[self.pk])
|
||||||
|
|
||||||
|
def clean(self):
|
||||||
|
|
||||||
|
# Validate interface types
|
||||||
|
if self.interface_a.type not in WIRELESS_IFACE_TYPES:
|
||||||
|
raise ValidationError({
|
||||||
|
'interface_a': f"{self.interface_a.get_type_display()} is not a wireless interface."
|
||||||
|
})
|
||||||
|
if self.interface_b.type not in WIRELESS_IFACE_TYPES:
|
||||||
|
raise ValidationError({
|
||||||
|
'interface_a': f"{self.interface_b.get_type_display()} is not a wireless interface."
|
||||||
|
})
|
||||||
|
|
||||||
|
def save(self, *args, **kwargs):
|
||||||
|
|
||||||
|
# Store the parent Device for the A and B interfaces
|
||||||
|
self._interface_a_device = self.interface_a.device
|
||||||
|
self._interface_b_device = self.interface_b.device
|
||||||
|
|
||||||
|
super().save(*args, **kwargs)
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
import django_tables2 as tables
|
import django_tables2 as tables
|
||||||
|
|
||||||
from .models import WirelessLAN
|
from .models import *
|
||||||
from utilities.tables import BaseTable, TagColumn, ToggleColumn
|
from utilities.tables import BaseTable, TagColumn, ToggleColumn
|
||||||
|
|
||||||
__all__ = (
|
__all__ = (
|
||||||
'WirelessLANTable',
|
'WirelessLANTable',
|
||||||
|
'WirelessLinkTable',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -21,3 +22,25 @@ class WirelessLANTable(BaseTable):
|
|||||||
model = WirelessLAN
|
model = WirelessLAN
|
||||||
fields = ('pk', 'ssid', 'description', 'vlan')
|
fields = ('pk', 'ssid', 'description', 'vlan')
|
||||||
default_columns = ('pk', 'ssid', 'description', 'vlan')
|
default_columns = ('pk', 'ssid', 'description', 'vlan')
|
||||||
|
|
||||||
|
|
||||||
|
class WirelessLinkTable(BaseTable):
|
||||||
|
pk = ToggleColumn()
|
||||||
|
id = tables.Column(
|
||||||
|
linkify=True,
|
||||||
|
verbose_name='ID'
|
||||||
|
)
|
||||||
|
interface_a = tables.Column(
|
||||||
|
linkify=True
|
||||||
|
)
|
||||||
|
interface_b = tables.Column(
|
||||||
|
linkify=True
|
||||||
|
)
|
||||||
|
tags = TagColumn(
|
||||||
|
url_name='wireless:wirelesslink_list'
|
||||||
|
)
|
||||||
|
|
||||||
|
class Meta(BaseTable.Meta):
|
||||||
|
model = WirelessLink
|
||||||
|
fields = ('pk', 'id', 'interface_a', 'interface_b', 'ssid', 'description')
|
||||||
|
default_columns = ('pk', 'id', 'interface_a', 'interface_b', 'ssid', 'description')
|
||||||
|
@ -19,4 +19,16 @@ urlpatterns = (
|
|||||||
path('wireless-lans/<int:pk>/changelog/', ObjectChangeLogView.as_view(), name='wirelesslan_changelog', kwargs={'model': WirelessLAN}),
|
path('wireless-lans/<int:pk>/changelog/', ObjectChangeLogView.as_view(), name='wirelesslan_changelog', kwargs={'model': WirelessLAN}),
|
||||||
path('wireless-lans/<int:pk>/journal/', ObjectJournalView.as_view(), name='wirelesslan_journal', kwargs={'model': WirelessLAN}),
|
path('wireless-lans/<int:pk>/journal/', ObjectJournalView.as_view(), name='wirelesslan_journal', kwargs={'model': WirelessLAN}),
|
||||||
|
|
||||||
|
# Wireless links
|
||||||
|
path('wireless-links/', views.WirelessLinkListView.as_view(), name='wirelesslink_list'),
|
||||||
|
path('wireless-links/add/', views.WirelessLinkEditView.as_view(), name='wirelesslink_add'),
|
||||||
|
path('wireless-links/import/', views.WirelessLinkBulkImportView.as_view(), name='wirelesslink_import'),
|
||||||
|
path('wireless-links/edit/', views.WirelessLinkBulkEditView.as_view(), name='wirelesslink_bulk_edit'),
|
||||||
|
path('wireless-links/delete/', views.WirelessLinkBulkDeleteView.as_view(), name='wirelesslink_bulk_delete'),
|
||||||
|
path('wireless-links/<int:pk>/', views.WirelessLinkView.as_view(), name='wirelesslink'),
|
||||||
|
path('wireless-links/<int:pk>/edit/', views.WirelessLinkEditView.as_view(), name='wirelesslink_edit'),
|
||||||
|
path('wireless-links/<int:pk>/delete/', views.WirelessLinkDeleteView.as_view(), name='wirelesslink_delete'),
|
||||||
|
path('wireless-links/<int:pk>/changelog/', ObjectChangeLogView.as_view(), name='wirelesslink_changelog', kwargs={'model': WirelessLink}),
|
||||||
|
path('wireless-links/<int:pk>/journal/', ObjectJournalView.as_view(), name='wirelesslink_journal', kwargs={'model': WirelessLink}),
|
||||||
|
|
||||||
)
|
)
|
||||||
|
@ -44,3 +44,46 @@ class WirelessLANBulkDeleteView(generic.BulkDeleteView):
|
|||||||
queryset = WirelessLAN.objects.all()
|
queryset = WirelessLAN.objects.all()
|
||||||
filterset = filtersets.WirelessLANFilterSet
|
filterset = filtersets.WirelessLANFilterSet
|
||||||
table = tables.WirelessLANTable
|
table = tables.WirelessLANTable
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
# Wireless Links
|
||||||
|
#
|
||||||
|
|
||||||
|
class WirelessLinkListView(generic.ObjectListView):
|
||||||
|
queryset = WirelessLink.objects.all()
|
||||||
|
filterset = filtersets.WirelessLinkFilterSet
|
||||||
|
filterset_form = forms.WirelessLinkFilterForm
|
||||||
|
table = tables.WirelessLinkTable
|
||||||
|
|
||||||
|
|
||||||
|
class WirelessLinkView(generic.ObjectView):
|
||||||
|
queryset = WirelessLink.objects.all()
|
||||||
|
|
||||||
|
|
||||||
|
class WirelessLinkEditView(generic.ObjectEditView):
|
||||||
|
queryset = WirelessLink.objects.all()
|
||||||
|
model_form = forms.WirelessLinkForm
|
||||||
|
|
||||||
|
|
||||||
|
class WirelessLinkDeleteView(generic.ObjectDeleteView):
|
||||||
|
queryset = WirelessLink.objects.all()
|
||||||
|
|
||||||
|
|
||||||
|
class WirelessLinkBulkImportView(generic.BulkImportView):
|
||||||
|
queryset = WirelessLink.objects.all()
|
||||||
|
model_form = forms.WirelessLinkCSVForm
|
||||||
|
table = tables.WirelessLinkTable
|
||||||
|
|
||||||
|
|
||||||
|
class WirelessLinkBulkEditView(generic.BulkEditView):
|
||||||
|
queryset = WirelessLink.objects.all()
|
||||||
|
filterset = filtersets.WirelessLinkFilterSet
|
||||||
|
table = tables.WirelessLinkTable
|
||||||
|
form = forms.WirelessLinkBulkEditForm
|
||||||
|
|
||||||
|
|
||||||
|
class WirelessLinkBulkDeleteView(generic.BulkDeleteView):
|
||||||
|
queryset = WirelessLink.objects.all()
|
||||||
|
filterset = filtersets.WirelessLinkFilterSet
|
||||||
|
table = tables.WirelessLinkTable
|
||||||
|
Loading…
Reference in New Issue
Block a user