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

Introduce the wireless app and SSID model

This commit is contained in:
jeremystretch
2021-10-12 12:27:12 -04:00
parent 8e1535f7ec
commit 8b80b0c3df
28 changed files with 470 additions and 0 deletions

View File

@ -308,6 +308,7 @@ class APIRootView(APIView):
('tenancy', reverse('tenancy-api:api-root', request=request, format=format)),
('users', reverse('users-api:api-root', request=request, format=format)),
('virtualization', reverse('virtualization-api:api-root', request=request, format=format)),
('wireless', reverse('wireless-api:api-root', request=request, format=format)),
)))

View File

@ -7,6 +7,7 @@ from ipam.graphql.schema import IPAMQuery
from tenancy.graphql.schema import TenancyQuery
from users.graphql.schema import UsersQuery
from virtualization.graphql.schema import VirtualizationQuery
from wireless.graphql.schema import WirelessQuery
class Query(
@ -17,6 +18,7 @@ class Query(
TenancyQuery,
UsersQuery,
VirtualizationQuery,
WirelessQuery,
graphene.ObjectType
):
pass

View File

@ -188,6 +188,19 @@ CONNECTIONS_MENU = Menu(
),
)
WIRELESS_MENU = Menu(
label='Wireless',
icon_class='mdi mdi-wifi',
groups=(
MenuGroup(
label='Wireless',
items=(
get_model_item('wireless', 'ssid', 'SSIDs'),
),
),
),
)
IPAM_MENU = Menu(
label='IPAM',
icon_class='mdi mdi-counter',
@ -343,6 +356,7 @@ MENUS = [
ORGANIZATION_MENU,
DEVICES_MENU,
CONNECTIONS_MENU,
WIRELESS_MENU,
IPAM_MENU,
VIRTUALIZATION_MENU,
CIRCUITS_MENU,

View File

@ -326,6 +326,7 @@ INSTALLED_APPS = [
'users',
'utilities',
'virtualization',
'wireless',
'django_rq', # Must come after extras to allow overriding management commands
'drf_yasg',
]

View File

@ -48,6 +48,7 @@ _patterns = [
path('tenancy/', include('tenancy.urls')),
path('user/', include('users.urls')),
path('virtualization/', include('virtualization.urls')),
path('wireless/', include('wireless.urls')),
# API
path('api/', APIRootView.as_view(), name='api-root'),
@ -58,6 +59,7 @@ _patterns = [
path('api/tenancy/', include('tenancy.api.urls')),
path('api/users/', include('users.api.urls')),
path('api/virtualization/', include('virtualization.api.urls')),
path('api/wireless/', include('wireless.api.urls')),
path('api/status/', StatusView.as_view(), name='api-status'),
path('api/docs/', schema_view.with_ui('swagger', cache_timeout=86400), name='api_docs'),
path('api/redoc/', schema_view.with_ui('redoc', cache_timeout=86400), name='api_redocs'),

View File

@ -0,0 +1,46 @@
{% 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">SSID</h5>
<div class="card-body">
<table class="table table-hover attr-table">
<tr>
<th scope="row">Name</th>
<td>{{ object.name }}</td>
</tr>
<tr>
<th scope="row">Description</th>
<td>{{ object.description|placeholder }}</td>
</tr>
<tr>
<th scope="row">VLAN</th>
<td>
{% if object.vlan %}
<a href="{{ object.vlan.get_absolute_url }}">{{ object.vlan }}</a>
{% else %}
<span class="text-muted">None</span>
{% endif %}
</td>
</tr>
</table>
</div>
</div>
{% include 'extras/inc/tags_panel.html' with tags=object.tags.all url='dcim:site_list' %}
{% plugin_left_page object %}
</div>
<div class="col col-md-6">
{% include 'inc/custom_fields_panel.html' %}
{% plugin_right_page object %}
</div>
</div>
<div class="row">
<div class="col col-md-12">
{% plugin_full_width_page object %}
</div>
</div>
{% endblock %}

View File

View File

View File

@ -0,0 +1,16 @@
from rest_framework import serializers
from netbox.api import WritableNestedSerializer
from wireless.models import *
__all__ = (
'NestedSSIDSerializer',
)
class NestedSSIDSerializer(WritableNestedSerializer):
url = serializers.HyperlinkedIdentityField(view_name='wireless-api:ssid-detail')
class Meta:
model = SSID
fields = ['id', 'url', 'display', 'name']

View File

@ -0,0 +1,21 @@
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 *
__all__ = (
'SSIDSerializer',
)
class SSIDSerializer(PrimaryModelSerializer):
url = serializers.HyperlinkedIdentityField(view_name='wireless-api:ssid-detail')
vlan = NestedVLANSerializer(required=False, allow_null=True)
class Meta:
model = SSID
fields = [
'id', 'url', 'display', 'name', 'description', 'vlan',
]

View File

@ -0,0 +1,12 @@
from netbox.api import OrderedDefaultRouter
from . import views
router = OrderedDefaultRouter()
router.APIRootView = views.WirelessRootView
# SSIDs
router.register('ssids', views.SSIDViewSet)
app_name = 'wireless-api'
urlpatterns = router.urls

View File

@ -0,0 +1,24 @@
from rest_framework.routers import APIRootView
from extras.api.views import CustomFieldModelViewSet
from wireless import filtersets
from wireless.models import *
from . import serializers
class WirelessRootView(APIRootView):
"""
Wireless API root view
"""
def get_view_name(self):
return 'Wireless'
#
# Providers
#
class SSIDViewSet(CustomFieldModelViewSet):
queryset = SSID.objects.prefetch_related('tags')
serializer_class = serializers.SSIDSerializer
filterset_class = filtersets.SSIDFilterSet

5
netbox/wireless/apps.py Normal file
View File

@ -0,0 +1,5 @@
from django.apps import AppConfig
class WirelessConfig(AppConfig):
name = 'wireless'

View File

@ -0,0 +1,31 @@
import django_filters
from django.db.models import Q
from extras.filters import TagFilter
from netbox.filtersets import PrimaryModelFilterSet
from .models import *
__all__ = (
'SSIDFilterSet',
)
class SSIDFilterSet(PrimaryModelFilterSet):
q = django_filters.CharFilter(
method='search',
label='Search',
)
tag = TagFilter()
class Meta:
model = SSID
fields = ['id', 'name']
def search(self, queryset, name, value):
if not value.strip():
return queryset
qs_filter = (
Q(name__icontains=value) |
Q(description__icontains=value)
)
return queryset.filter(qs_filter)

View File

@ -0,0 +1,4 @@
from .models import *
from .filtersets import *
from .bulk_edit import *
from .bulk_import import *

View File

@ -0,0 +1,29 @@
from django import forms
from dcim.models import *
from extras.forms import AddRemoveTagsForm, CustomFieldModelBulkEditForm
from ipam.models import VLAN
from utilities.forms import BootstrapMixin, DynamicModelChoiceField
__all__ = (
'SSIDBulkEditForm',
)
class SSIDBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldModelBulkEditForm):
pk = forms.ModelMultipleChoiceField(
queryset=PowerFeed.objects.all(),
widget=forms.MultipleHiddenInput
)
vlan = DynamicModelChoiceField(
queryset=VLAN.objects.all(),
required=False,
)
description = forms.CharField(
required=False
)
class Meta:
nullable_fields = [
'vlan', 'description',
]

View File

@ -0,0 +1,20 @@
from extras.forms import CustomFieldModelCSVForm
from ipam.models import VLAN
from utilities.forms import CSVModelChoiceField
from wireless.models import SSID
__all__ = (
'SSIDCSVForm',
)
class SSIDCSVForm(CustomFieldModelCSVForm):
vlan = CSVModelChoiceField(
queryset=VLAN.objects.all(),
to_field_name='name',
help_text='Bridged VLAN'
)
class Meta:
model = SSID
fields = ('name', 'description', 'vlan')

View File

@ -0,0 +1,19 @@
from django import forms
from django.utils.translation import gettext as _
from dcim.models import *
from extras.forms import CustomFieldModelFilterForm
from utilities.forms import BootstrapMixin, TagFilterField
class SSIDFilterForm(BootstrapMixin, CustomFieldModelFilterForm):
model = PowerFeed
field_groups = [
['q', 'tag'],
]
q = forms.CharField(
required=False,
widget=forms.TextInput(attrs={'placeholder': _('All Fields')}),
label=_('Search')
)
tag = TagFilterField(model)

View File

@ -0,0 +1,32 @@
from dcim.constants import *
from dcim.models import *
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 SSID
__all__ = (
'SSIDForm',
)
class SSIDForm(BootstrapMixin, CustomFieldModelForm):
vlan = DynamicModelChoiceField(
queryset=VLAN.objects.all(),
required=False
)
tags = DynamicModelMultipleChoiceField(
queryset=Tag.objects.all(),
required=False
)
class Meta:
model = SSID
fields = [
'name', 'description', 'vlan', 'tags',
]
fieldsets = (
('SSID', ('name', 'description', 'tags')),
('VLAN', ('vlan',)),
)

View File

View File

@ -0,0 +1,9 @@
import graphene
from netbox.graphql.fields import ObjectField, ObjectListField
from .types import *
class WirelessQuery(graphene.ObjectType):
ssid = ObjectField(SSIDType)
ssid_list = ObjectListField(SSIDType)

View File

@ -0,0 +1,14 @@
from wireless import filtersets, models
from netbox.graphql.types import ObjectType
__all__ = (
'SSIDType',
)
class SSIDType(ObjectType):
class Meta:
model = models.SSID
fields = '__all__'
filterset_class = filtersets.SSIDFilterSet

View File

@ -0,0 +1,36 @@
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='SSID',
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)),
('name', 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': 'SSID',
'verbose_name_plural': 'SSIDs',
'ordering': ('name', 'pk'),
},
),
]

View File

40
netbox/wireless/models.py Normal file
View File

@ -0,0 +1,40 @@
from django.db import models
from extras.utils import extras_features
from netbox.models import PrimaryModel
from utilities.querysets import RestrictedQuerySet
__all__ = (
'SSID',
)
@extras_features('custom_fields', 'custom_links', 'export_templates', 'tags', 'webhooks')
class SSID(PrimaryModel):
"""
A service set identifier belonging to a wireless network.
"""
name = models.CharField(
max_length=32
)
vlan = models.ForeignKey(
to='ipam.VLAN',
on_delete=models.PROTECT,
blank=True,
null=True,
verbose_name='VLAN'
)
description = models.CharField(
max_length=200,
blank=True
)
objects = RestrictedQuerySet.as_manager()
class Meta:
ordering = ('name', 'pk')
verbose_name = 'SSID'
verbose_name_plural = 'SSIDs'
def __str__(self):
return self.name

24
netbox/wireless/tables.py Normal file
View File

@ -0,0 +1,24 @@
import django_tables2 as tables
from .models import SSID
from utilities.tables import BaseTable, TagColumn, ToggleColumn
__all__ = (
'SSIDTable',
)
class SSIDTable(BaseTable):
pk = ToggleColumn()
id = tables.Column(
linkify=True,
verbose_name='ID'
)
tags = TagColumn(
url_name='dcim:cable_list'
)
class Meta(BaseTable.Meta):
model = SSID
fields = ('pk', 'id', 'name', 'description', 'vlan')
default_columns = ('pk', 'name', 'description', 'vlan')

22
netbox/wireless/urls.py Normal file
View File

@ -0,0 +1,22 @@
from django.urls import path
from extras.views import ObjectChangeLogView, ObjectJournalView
from . import views
from .models import *
app_name = 'wireless'
urlpatterns = (
# SSIDs
path('ssids/', views.SSIDListView.as_view(), name='ssid_list'),
path('ssids/add/', views.SSIDEditView.as_view(), name='ssid_add'),
path('ssids/import/', views.SSIDBulkImportView.as_view(), name='ssid_import'),
path('ssids/edit/', views.SSIDBulkEditView.as_view(), name='ssid_bulk_edit'),
path('ssids/delete/', views.SSIDBulkDeleteView.as_view(), name='ssid_bulk_delete'),
path('ssids/<int:pk>/', views.SSIDView.as_view(), name='ssid'),
path('ssids/<int:pk>/edit/', views.SSIDEditView.as_view(), name='ssid_edit'),
path('ssids/<int:pk>/delete/', views.SSIDDeleteView.as_view(), name='ssid_delete'),
path('ssids/<int:pk>/changelog/', ObjectChangeLogView.as_view(), name='ssid_changelog', kwargs={'model': SSID}),
path('ssids/<int:pk>/journal/', ObjectJournalView.as_view(), name='ssid_journal', kwargs={'model': SSID}),
)

46
netbox/wireless/views.py Normal file
View File

@ -0,0 +1,46 @@
from netbox.views import generic
from . import filtersets, forms, tables
from .models import *
#
# SSIDs
#
class SSIDListView(generic.ObjectListView):
queryset = SSID.objects.all()
filterset = filtersets.SSIDFilterSet
filterset_form = forms.SSIDFilterForm
table = tables.SSIDTable
class SSIDView(generic.ObjectView):
queryset = SSID.objects.prefetch_related('power_panel', 'rack')
class SSIDEditView(generic.ObjectEditView):
queryset = SSID.objects.all()
model_form = forms.SSIDForm
class SSIDDeleteView(generic.ObjectDeleteView):
queryset = SSID.objects.all()
class SSIDBulkImportView(generic.BulkImportView):
queryset = SSID.objects.all()
model_form = forms.SSIDCSVForm
table = tables.SSIDTable
class SSIDBulkEditView(generic.BulkEditView):
queryset = SSID.objects.prefetch_related('power_panel', 'rack')
filterset = filtersets.SSIDFilterSet
table = tables.SSIDTable
form = forms.SSIDBulkEditForm
class SSIDBulkDeleteView(generic.BulkDeleteView):
queryset = SSID.objects.prefetch_related('power_panel', 'rack')
filterset = filtersets.SSIDFilterSet
table = tables.SSIDTable