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:
@ -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)),
|
||||
)))
|
||||
|
||||
|
||||
|
@ -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
|
||||
|
@ -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,
|
||||
|
@ -326,6 +326,7 @@ INSTALLED_APPS = [
|
||||
'users',
|
||||
'utilities',
|
||||
'virtualization',
|
||||
'wireless',
|
||||
'django_rq', # Must come after extras to allow overriding management commands
|
||||
'drf_yasg',
|
||||
]
|
||||
|
@ -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'),
|
||||
|
46
netbox/templates/wireless/ssid.html
Normal file
46
netbox/templates/wireless/ssid.html
Normal 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 %}
|
0
netbox/wireless/__init__.py
Normal file
0
netbox/wireless/__init__.py
Normal file
0
netbox/wireless/api/__init__.py
Normal file
0
netbox/wireless/api/__init__.py
Normal file
16
netbox/wireless/api/nested_serializers.py
Normal file
16
netbox/wireless/api/nested_serializers.py
Normal 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']
|
21
netbox/wireless/api/serializers.py
Normal file
21
netbox/wireless/api/serializers.py
Normal 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',
|
||||
]
|
12
netbox/wireless/api/urls.py
Normal file
12
netbox/wireless/api/urls.py
Normal 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
|
24
netbox/wireless/api/views.py
Normal file
24
netbox/wireless/api/views.py
Normal 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
5
netbox/wireless/apps.py
Normal file
@ -0,0 +1,5 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class WirelessConfig(AppConfig):
|
||||
name = 'wireless'
|
31
netbox/wireless/filtersets.py
Normal file
31
netbox/wireless/filtersets.py
Normal 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)
|
4
netbox/wireless/forms/__init__.py
Normal file
4
netbox/wireless/forms/__init__.py
Normal file
@ -0,0 +1,4 @@
|
||||
from .models import *
|
||||
from .filtersets import *
|
||||
from .bulk_edit import *
|
||||
from .bulk_import import *
|
29
netbox/wireless/forms/bulk_edit.py
Normal file
29
netbox/wireless/forms/bulk_edit.py
Normal 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',
|
||||
]
|
20
netbox/wireless/forms/bulk_import.py
Normal file
20
netbox/wireless/forms/bulk_import.py
Normal 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')
|
19
netbox/wireless/forms/filtersets.py
Normal file
19
netbox/wireless/forms/filtersets.py
Normal 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)
|
32
netbox/wireless/forms/models.py
Normal file
32
netbox/wireless/forms/models.py
Normal 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',)),
|
||||
)
|
0
netbox/wireless/graphql/__init__.py
Normal file
0
netbox/wireless/graphql/__init__.py
Normal file
9
netbox/wireless/graphql/schema.py
Normal file
9
netbox/wireless/graphql/schema.py
Normal 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)
|
14
netbox/wireless/graphql/types.py
Normal file
14
netbox/wireless/graphql/types.py
Normal 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
|
36
netbox/wireless/migrations/0001_initial.py
Normal file
36
netbox/wireless/migrations/0001_initial.py
Normal 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'),
|
||||
},
|
||||
),
|
||||
]
|
0
netbox/wireless/migrations/__init__.py
Normal file
0
netbox/wireless/migrations/__init__.py
Normal file
40
netbox/wireless/models.py
Normal file
40
netbox/wireless/models.py
Normal 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
24
netbox/wireless/tables.py
Normal 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
22
netbox/wireless/urls.py
Normal 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
46
netbox/wireless/views.py
Normal 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
|
Reference in New Issue
Block a user