diff --git a/netbox/dcim/migrations/0136_wireless.py b/netbox/dcim/migrations/0136_wireless.py
index 108e63802..3b33f7d3f 100644
--- a/netbox/dcim/migrations/0136_wireless.py
+++ b/netbox/dcim/migrations/0136_wireless.py
@@ -1,10 +1,11 @@
+# Generated by Django 3.2.8 on 2021-10-13 13:44
+
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
- ('wireless', '0001_initial'),
('dcim', '0135_location_tenant'),
]
@@ -19,9 +20,4 @@ class Migration(migrations.Migration):
name='rf_channel_width',
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'),
- ),
]
diff --git a/netbox/dcim/migrations/0137_wireless.py b/netbox/dcim/migrations/0137_wireless.py
new file mode 100644
index 000000000..9108735a1
--- /dev/null
+++ b/netbox/dcim/migrations/0137_wireless.py
@@ -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'),
+ ),
+ ]
diff --git a/netbox/netbox/navigation_menu.py b/netbox/netbox/navigation_menu.py
index 073189d31..b3e11f6ce 100644
--- a/netbox/netbox/navigation_menu.py
+++ b/netbox/netbox/navigation_menu.py
@@ -196,6 +196,7 @@ WIRELESS_MENU = Menu(
label='Wireless',
items=(
get_model_item('wireless', 'wirelesslan', 'Wireless LANs'),
+ get_model_item('wireless', 'wirelesslink', 'Wirelesss Links', actions=['import']),
),
),
),
diff --git a/netbox/templates/wireless/inc/wirelesslink_interface.html b/netbox/templates/wireless/inc/wirelesslink_interface.html
new file mode 100644
index 000000000..9c4669ad1
--- /dev/null
+++ b/netbox/templates/wireless/inc/wirelesslink_interface.html
@@ -0,0 +1,20 @@
+
diff --git a/netbox/templates/wireless/wirelesslink.html b/netbox/templates/wireless/wirelesslink.html
new file mode 100644
index 000000000..6196adae4
--- /dev/null
+++ b/netbox/templates/wireless/wirelesslink.html
@@ -0,0 +1,48 @@
+{% extends 'generic/object.html' %}
+{% load helpers %}
+{% load plugins %}
+
+{% block content %}
+
+
+
+
+
+ {% include 'wireless/inc/wirelesslink_interface.html' with interface=object.interface_a %}
+
+
+
+
+
+
+
+ SSID |
+ {{ object.ssid|placeholder }} |
+
+
+ Description |
+ {{ object.description|placeholder }} |
+
+
+
+
+ {% plugin_left_page object %}
+
+
+
+
+
+ {% include 'wireless/inc/wirelesslink_interface.html' with interface=object.interface_b %}
+
+
+ {% 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 %}
+
+
+
+
+ {% plugin_full_width_page object %}
+
+
+{% endblock %}
diff --git a/netbox/wireless/api/nested_serializers.py b/netbox/wireless/api/nested_serializers.py
index e290653a2..5a8cf6671 100644
--- a/netbox/wireless/api/nested_serializers.py
+++ b/netbox/wireless/api/nested_serializers.py
@@ -5,12 +5,21 @@ from wireless.models import *
__all__ = (
'NestedWirelessLANSerializer',
+ 'NestedWirelessLinkSerializer',
)
class NestedWirelessLANSerializer(WritableNestedSerializer):
- url = serializers.HyperlinkedIdentityField(view_name='wireless-api:ssid-detail')
+ url = serializers.HyperlinkedIdentityField(view_name='wireless-api:wirelesslan-detail')
class Meta:
model = WirelessLAN
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']
diff --git a/netbox/wireless/api/serializers.py b/netbox/wireless/api/serializers.py
index 08642259f..90515f53e 100644
--- a/netbox/wireless/api/serializers.py
+++ b/netbox/wireless/api/serializers.py
@@ -1,16 +1,19 @@
from rest_framework import serializers
+from dcim.api.serializers import NestedInterfaceSerializer
from ipam.api.serializers import NestedVLANSerializer
from netbox.api.serializers import PrimaryModelSerializer
from wireless.models import *
+from .nested_serializers import *
__all__ = (
'WirelessLANSerializer',
+ 'WirelessLinkSerializer',
)
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)
class Meta:
@@ -18,3 +21,15 @@ class WirelessLANSerializer(PrimaryModelSerializer):
fields = [
'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',
+ ]
diff --git a/netbox/wireless/api/urls.py b/netbox/wireless/api/urls.py
index 638f31bbf..431bb05f8 100644
--- a/netbox/wireless/api/urls.py
+++ b/netbox/wireless/api/urls.py
@@ -6,6 +6,7 @@ router = OrderedDefaultRouter()
router.APIRootView = views.WirelessRootView
router.register('wireless-lans', views.WirelessLANViewSet)
+router.register('wireless-links', views.WirelessLinkViewSet)
app_name = 'wireless-api'
urlpatterns = router.urls
diff --git a/netbox/wireless/api/views.py b/netbox/wireless/api/views.py
index a09b2e23d..aa361a7f7 100644
--- a/netbox/wireless/api/views.py
+++ b/netbox/wireless/api/views.py
@@ -14,11 +14,13 @@ class WirelessRootView(APIRootView):
return 'Wireless'
-#
-# Providers
-#
-
class WirelessLANViewSet(CustomFieldModelViewSet):
- queryset = WirelessLAN.objects.prefetch_related('tags')
+ queryset = WirelessLAN.objects.prefetch_related('vlan', 'tags')
serializer_class = serializers.WirelessLANSerializer
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
diff --git a/netbox/wireless/filtersets.py b/netbox/wireless/filtersets.py
index c148354a0..7341ada9d 100644
--- a/netbox/wireless/filtersets.py
+++ b/netbox/wireless/filtersets.py
@@ -7,6 +7,7 @@ from .models import *
__all__ = (
'WirelessLANFilterSet',
+ 'WirelessLinkFilterSet',
)
@@ -29,3 +30,24 @@ class WirelessLANFilterSet(PrimaryModelFilterSet):
Q(description__icontains=value)
)
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)
diff --git a/netbox/wireless/forms/bulk_edit.py b/netbox/wireless/forms/bulk_edit.py
index c11a16239..65666ccb1 100644
--- a/netbox/wireless/forms/bulk_edit.py
+++ b/netbox/wireless/forms/bulk_edit.py
@@ -4,9 +4,11 @@ from dcim.models import *
from extras.forms import AddRemoveTagsForm, CustomFieldModelBulkEditForm
from ipam.models import VLAN
from utilities.forms import BootstrapMixin, DynamicModelChoiceField
+from wireless.constants import SSID_MAX_LENGTH
__all__ = (
'WirelessLANBulkEditForm',
+ 'WirelessLinkBulkEditForm',
)
@@ -19,11 +21,30 @@ class WirelessLANBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldMode
queryset=VLAN.objects.all(),
required=False,
)
+ ssid = forms.CharField(
+ max_length=SSID_MAX_LENGTH,
+ required=False
+ )
description = forms.CharField(
required=False
)
class Meta:
- nullable_fields = [
- 'vlan', 'description',
- ]
+ nullable_fields = ['vlan', 'ssid', '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']
diff --git a/netbox/wireless/forms/bulk_import.py b/netbox/wireless/forms/bulk_import.py
index 5dc07f91a..caf322dc1 100644
--- a/netbox/wireless/forms/bulk_import.py
+++ b/netbox/wireless/forms/bulk_import.py
@@ -1,10 +1,12 @@
+from dcim.models import Interface
from extras.forms import CustomFieldModelCSVForm
from ipam.models import VLAN
from utilities.forms import CSVModelChoiceField
-from wireless.models import WirelessLAN
+from wireless.models import *
__all__ = (
'WirelessLANCSVForm',
+ 'WirelessLinkCSVForm',
)
@@ -18,3 +20,16 @@ class WirelessLANCSVForm(CustomFieldModelCSVForm):
class Meta:
model = WirelessLAN
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')
diff --git a/netbox/wireless/forms/filtersets.py b/netbox/wireless/forms/filtersets.py
index 99e38918e..fa1912099 100644
--- a/netbox/wireless/forms/filtersets.py
+++ b/netbox/wireless/forms/filtersets.py
@@ -3,7 +3,12 @@ from django.utils.translation import gettext as _
from extras.forms import CustomFieldModelFilterForm
from utilities.forms import BootstrapMixin, TagFilterField
-from .models import WirelessLAN
+from wireless.models import *
+
+__all__ = (
+ 'WirelessLANFilterForm',
+ 'WirelessLinkFilterForm',
+)
class WirelessLANFilterForm(BootstrapMixin, CustomFieldModelFilterForm):
@@ -16,4 +21,25 @@ class WirelessLANFilterForm(BootstrapMixin, CustomFieldModelFilterForm):
widget=forms.TextInput(attrs={'placeholder': _('All Fields')}),
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)
diff --git a/netbox/wireless/forms/models.py b/netbox/wireless/forms/models.py
index 95b43c7d3..08e864340 100644
--- a/netbox/wireless/forms/models.py
+++ b/netbox/wireless/forms/models.py
@@ -1,11 +1,13 @@
+from dcim.models import Interface
from extras.forms import CustomFieldModelForm
from extras.models import Tag
from ipam.models import VLAN
from utilities.forms import BootstrapMixin, DynamicModelChoiceField, DynamicModelMultipleChoiceField
-from wireless.models import WirelessLAN
+from wireless.models import *
__all__ = (
'WirelessLANForm',
+ 'WirelessLinkForm',
)
@@ -28,3 +30,28 @@ class WirelessLANForm(BootstrapMixin, CustomFieldModelForm):
('Wireless LAN', ('ssid', 'description', 'tags')),
('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',
+ ]
diff --git a/netbox/wireless/migrations/0001_initial.py b/netbox/wireless/migrations/0001_initial.py
deleted file mode 100644
index c93a17190..000000000
--- a/netbox/wireless/migrations/0001_initial.py
+++ /dev/null
@@ -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'),
- },
- ),
- ]
diff --git a/netbox/wireless/migrations/0001_wireless.py b/netbox/wireless/migrations/0001_wireless.py
new file mode 100644
index 000000000..2fb07e5fd
--- /dev/null
+++ b/netbox/wireless/migrations/0001_wireless.py
@@ -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')},
+ },
+ ),
+ ]
diff --git a/netbox/wireless/models.py b/netbox/wireless/models.py
index 363631ef5..41f3dbf6d 100644
--- a/netbox/wireless/models.py
+++ b/netbox/wireless/models.py
@@ -6,19 +6,22 @@ from dcim.constants import WIRELESS_IFACE_TYPES
from extras.utils import extras_features
from netbox.models import BigIDModel, PrimaryModel
from utilities.querysets import RestrictedQuerySet
+from .constants import SSID_MAX_LENGTH
__all__ = (
'WirelessLAN',
+ 'WirelessLink',
)
@extras_features('custom_fields', 'custom_links', 'export_templates', 'tags', 'webhooks')
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(
- max_length=32
+ max_length=SSID_MAX_LENGTH,
+ verbose_name='SSID'
)
vlan = models.ForeignKey(
to='ipam.VLAN',
@@ -42,4 +45,78 @@ class WirelessLAN(PrimaryModel):
return self.ssid
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)
diff --git a/netbox/wireless/tables.py b/netbox/wireless/tables.py
index 133353f57..31c9e56a8 100644
--- a/netbox/wireless/tables.py
+++ b/netbox/wireless/tables.py
@@ -1,10 +1,11 @@
import django_tables2 as tables
-from .models import WirelessLAN
+from .models import *
from utilities.tables import BaseTable, TagColumn, ToggleColumn
__all__ = (
'WirelessLANTable',
+ 'WirelessLinkTable',
)
@@ -21,3 +22,25 @@ class WirelessLANTable(BaseTable):
model = WirelessLAN
fields = ('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')
diff --git a/netbox/wireless/urls.py b/netbox/wireless/urls.py
index c30ca472c..21d704e6a 100644
--- a/netbox/wireless/urls.py
+++ b/netbox/wireless/urls.py
@@ -19,4 +19,16 @@ urlpatterns = (
path('wireless-lans/
/changelog/', ObjectChangeLogView.as_view(), name='wirelesslan_changelog', kwargs={'model': WirelessLAN}),
path('wireless-lans//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//', views.WirelessLinkView.as_view(), name='wirelesslink'),
+ path('wireless-links//edit/', views.WirelessLinkEditView.as_view(), name='wirelesslink_edit'),
+ path('wireless-links//delete/', views.WirelessLinkDeleteView.as_view(), name='wirelesslink_delete'),
+ path('wireless-links//changelog/', ObjectChangeLogView.as_view(), name='wirelesslink_changelog', kwargs={'model': WirelessLink}),
+ path('wireless-links//journal/', ObjectJournalView.as_view(), name='wirelesslink_journal', kwargs={'model': WirelessLink}),
+
)
diff --git a/netbox/wireless/views.py b/netbox/wireless/views.py
index 6e1c0b1b7..041ffbd42 100644
--- a/netbox/wireless/views.py
+++ b/netbox/wireless/views.py
@@ -44,3 +44,46 @@ class WirelessLANBulkDeleteView(generic.BulkDeleteView):
queryset = WirelessLAN.objects.all()
filterset = filtersets.WirelessLANFilterSet
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