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

Add WirelessLANGroup model

This commit is contained in:
jeremystretch
2021-10-13 16:40:12 -04:00
parent 43f2d4a331
commit 01f791a44e
19 changed files with 429 additions and 16 deletions

View File

@ -168,6 +168,7 @@ CONNECTIONS_MENU = Menu(
label='Connections',
items=(
get_model_item('dcim', 'cable', 'Cables', actions=['import']),
get_model_item('wireless', 'wirelesslink', 'Wirelesss Links', actions=['import']),
MenuItem(
link='dcim:interface_connections_list',
link_text='Interface Connections',
@ -196,7 +197,7 @@ WIRELESS_MENU = Menu(
label='Wireless',
items=(
get_model_item('wireless', 'wirelesslan', 'Wireless LANs'),
get_model_item('wireless', 'wirelesslink', 'Wirelesss Links', actions=['import']),
get_model_item('wireless', 'wirelesslangroup', 'Wireless LAN Groups'),
),
),
),

View File

@ -13,6 +13,16 @@
<th scope="row">SSID</th>
<td>{{ object.ssid }}</td>
</tr>
<tr>
<td>Group</td>
<td>
{% if object.group %}
<a href="{{ object.group.get_absolute_url }}">{{ object.group }}</a>
{% else %}
<span class="text-muted">None</span>
{% endif %}
</td>
</tr>
<tr>
<th scope="row">Description</th>
<td>{{ object.description|placeholder }}</td>

View File

@ -0,0 +1,72 @@
{% extends 'generic/object.html' %}
{% load helpers %}
{% load plugins %}
{% block breadcrumbs %}
{{ block.super }}
{% for group in object.get_ancestors %}
<li class="breadcrumb-item"><a href="{% url 'wireless:wirelesslangroup_list' %}?parent_id={{ group.pk }}">{{ group }}</a></li>
{% endfor %}
{% endblock %}
{% block content %}
<div class="row mb-3">
<div class="col col-md-6">
<div class="card">
<h5 class="card-header">Wireless LAN Group</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">Parent</th>
<td>
{% if object.parent %}
<a href="{{ object.parent.get_absolute_url }}">{{ object.parent }}</a>
{% else %}
<span class="text-muted">&mdash;</span>
{% endif %}
</td>
</tr>
<tr>
<th scope="row">Wireless LANs</th>
<td>
<a href="{% url 'wireless:wirelesslan_list' %}?group_id={{ object.pk }}">{{ wirelesslans_table.rows|length }}</a>
</td>
</tr>
</table>
</div>
</div>
{% 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 mb-3">
<div class="col col-md-12">
<div class="card">
<div class="card-header">Wireless LANs</div>
<div class="card-body">
{% include 'inc/table.html' with table=wirelesslans_table %}
</div>
{% if perms.wireless.add_wirelesslan %}
<div class="card-footer text-end noprint">
<a href="{% url 'wireless:wirelesslan_add' %}?group={{ object.pk }}" class="btn btn-sm btn-primary">
<span class="mdi mdi-plus-thick" aria-hidden="true"></span> Add Wireless LAN
</a>
</div>
{% endif %}
</div>
{% include 'inc/paginator.html' with paginator=wirelesslans_table.paginator page=wirelesslans_table.page %}
{% plugin_full_width_page object %}
</div>
</div>
{% endblock %}

View File

@ -5,10 +5,21 @@ from wireless.models import *
__all__ = (
'NestedWirelessLANSerializer',
'NestedWirelessLANGroupSerializer',
'NestedWirelessLinkSerializer',
)
class NestedWirelessLANGroupSerializer(WritableNestedSerializer):
url = serializers.HyperlinkedIdentityField(view_name='wireless-api:wirelesslangroup-detail')
wirelesslan_count = serializers.IntegerField(read_only=True)
_depth = serializers.IntegerField(source='level', read_only=True)
class Meta:
model = WirelessLANGroup
fields = ['id', 'url', 'display', 'name', 'slug', 'wirelesslan_count', '_depth']
class NestedWirelessLANSerializer(WritableNestedSerializer):
url = serializers.HyperlinkedIdentityField(view_name='wireless-api:wirelesslan-detail')

View File

@ -4,7 +4,7 @@ from dcim.choices import LinkStatusChoices
from dcim.api.serializers import NestedInterfaceSerializer
from ipam.api.serializers import NestedVLANSerializer
from netbox.api import ChoiceField
from netbox.api.serializers import PrimaryModelSerializer
from netbox.api.serializers import NestedGroupModelSerializer, PrimaryModelSerializer
from wireless.models import *
from .nested_serializers import *
@ -14,6 +14,19 @@ __all__ = (
)
class WirelessLANGroupSerializer(NestedGroupModelSerializer):
url = serializers.HyperlinkedIdentityField(view_name='wireless-api:wirelesslangroup-detail')
parent = NestedWirelessLANGroupSerializer(required=False, allow_null=True)
wirelesslan_count = serializers.IntegerField(read_only=True)
class Meta:
model = WirelessLANGroup
fields = [
'id', 'url', 'display', 'name', 'slug', 'parent', 'description', 'custom_fields', 'created', 'last_updated',
'wirelesslan_count', '_depth',
]
class WirelessLANSerializer(PrimaryModelSerializer):
url = serializers.HyperlinkedIdentityField(view_name='wireless-api:wirelesslan-detail')
vlan = NestedVLANSerializer(required=False, allow_null=True)

View File

@ -5,6 +5,7 @@ from . import views
router = OrderedDefaultRouter()
router.APIRootView = views.WirelessRootView
router.register('wireless-lan-groupss', views.WirelessLANGroupViewSet)
router.register('wireless-lans', views.WirelessLANViewSet)
router.register('wireless-links', views.WirelessLinkViewSet)

View File

@ -14,6 +14,18 @@ class WirelessRootView(APIRootView):
return 'Wireless'
class WirelessLANGroupViewSet(CustomFieldModelViewSet):
queryset = WirelessLANGroup.objects.add_related_count(
WirelessLANGroup.objects.all(),
WirelessLAN,
'group',
'wirelesslan_count',
cumulative=True
)
serializer_class = serializers.WirelessLANGroupSerializer
filterset_class = filtersets.WirelessLANGroupFilterSet
class WirelessLANViewSet(CustomFieldModelViewSet):
queryset = WirelessLAN.objects.prefetch_related('vlan', 'tags')
serializer_class = serializers.WirelessLANSerializer

View File

@ -3,15 +3,31 @@ from django.db.models import Q
from dcim.choices import LinkStatusChoices
from extras.filters import TagFilter
from netbox.filtersets import PrimaryModelFilterSet
from netbox.filtersets import OrganizationalModelFilterSet, PrimaryModelFilterSet
from .models import *
__all__ = (
'WirelessLANFilterSet',
'WirelessLANGroupFilterSet',
'WirelessLinkFilterSet',
)
class WirelessLANGroupFilterSet(OrganizationalModelFilterSet):
parent_id = django_filters.ModelMultipleChoiceFilter(
queryset=WirelessLANGroup.objects.all()
)
parent = django_filters.ModelMultipleChoiceFilter(
field_name='parent__slug',
queryset=WirelessLANGroup.objects.all(),
to_field_name='slug'
)
class Meta:
model = WirelessLANGroup
fields = ['id', 'name', 'slug', 'description']
class WirelessLANFilterSet(PrimaryModelFilterSet):
q = django_filters.CharFilter(
method='search',

View File

@ -9,15 +9,38 @@ from wireless.models import *
__all__ = (
'WirelessLANBulkEditForm',
'WirelessLANGroupBulkEditForm',
'WirelessLinkBulkEditForm',
)
class WirelessLANGroupBulkEditForm(BootstrapMixin, CustomFieldModelBulkEditForm):
pk = forms.ModelMultipleChoiceField(
queryset=WirelessLANGroup.objects.all(),
widget=forms.MultipleHiddenInput
)
parent = DynamicModelChoiceField(
queryset=WirelessLANGroup.objects.all(),
required=False
)
description = forms.CharField(
max_length=200,
required=False
)
class Meta:
nullable_fields = ['parent', 'description']
class WirelessLANBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldModelBulkEditForm):
pk = forms.ModelMultipleChoiceField(
queryset=WirelessLAN.objects.all(),
widget=forms.MultipleHiddenInput
)
group = DynamicModelChoiceField(
queryset=WirelessLANGroup.objects.all(),
required=False
)
vlan = DynamicModelChoiceField(
queryset=VLAN.objects.all(),
required=False,
@ -31,7 +54,7 @@ class WirelessLANBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldMode
)
class Meta:
nullable_fields = ['vlan', 'ssid', 'description']
nullable_fields = ['ssid', 'group', 'vlan', 'description']
class WirelessLinkBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldModelBulkEditForm):

View File

@ -2,16 +2,37 @@ from dcim.choices import LinkStatusChoices
from dcim.models import Interface
from extras.forms import CustomFieldModelCSVForm
from ipam.models import VLAN
from utilities.forms import CSVChoiceField, CSVModelChoiceField
from utilities.forms import CSVChoiceField, CSVModelChoiceField, SlugField
from wireless.models import *
__all__ = (
'WirelessLANCSVForm',
'WirelessLANGroupCSVForm',
'WirelessLinkCSVForm',
)
class WirelessLANGroupCSVForm(CustomFieldModelCSVForm):
parent = CSVModelChoiceField(
queryset=WirelessLANGroup.objects.all(),
required=False,
to_field_name='name',
help_text='Parent group'
)
slug = SlugField()
class Meta:
model = WirelessLANGroup
fields = ('name', 'slug', 'parent', 'description')
class WirelessLANCSVForm(CustomFieldModelCSVForm):
group = CSVModelChoiceField(
queryset=WirelessLANGroup.objects.all(),
required=False,
to_field_name='name',
help_text='Assigned group'
)
vlan = CSVModelChoiceField(
queryset=VLAN.objects.all(),
to_field_name='name',
@ -20,7 +41,7 @@ class WirelessLANCSVForm(CustomFieldModelCSVForm):
class Meta:
model = WirelessLAN
fields = ('ssid', 'description', 'vlan')
fields = ('ssid', 'group', 'description', 'vlan')
class WirelessLinkCSVForm(CustomFieldModelCSVForm):

View File

@ -3,19 +3,38 @@ from django.utils.translation import gettext as _
from dcim.choices import LinkStatusChoices
from extras.forms import CustomFieldModelFilterForm
from utilities.forms import add_blank_choice, BootstrapMixin, StaticSelect, TagFilterField
from utilities.forms import (
add_blank_choice, BootstrapMixin, DynamicModelMultipleChoiceField, StaticSelect, TagFilterField,
)
from wireless.models import *
__all__ = (
'WirelessLANFilterForm',
'WirelessLANGroupFilterForm',
'WirelessLinkFilterForm',
)
class WirelessLANGroupFilterForm(BootstrapMixin, CustomFieldModelFilterForm):
model = WirelessLANGroup
q = forms.CharField(
required=False,
widget=forms.TextInput(attrs={'placeholder': _('All Fields')}),
label=_('Search')
)
parent_id = DynamicModelMultipleChoiceField(
queryset=WirelessLANGroup.objects.all(),
required=False,
label=_('Parent group'),
fetch_trigger='open'
)
class WirelessLANFilterForm(BootstrapMixin, CustomFieldModelFilterForm):
model = WirelessLAN
field_groups = [
['q', 'tag'],
('q', 'tag'),
('group_id',),
]
q = forms.CharField(
required=False,
@ -26,6 +45,13 @@ class WirelessLANFilterForm(BootstrapMixin, CustomFieldModelFilterForm):
required=False,
label='SSID'
)
group_id = DynamicModelMultipleChoiceField(
queryset=WirelessLANGroup.objects.all(),
required=False,
null_option='None',
label=_('Group'),
fetch_trigger='open'
)
tag = TagFilterField(model)

View File

@ -2,16 +2,37 @@ 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, StaticSelect
from utilities.forms import (
BootstrapMixin, DynamicModelChoiceField, DynamicModelMultipleChoiceField, SlugField, StaticSelect,
)
from wireless.models import *
__all__ = (
'WirelessLANForm',
'WirelessLANGroupForm',
'WirelessLinkForm',
)
class WirelessLANGroupForm(BootstrapMixin, CustomFieldModelForm):
parent = DynamicModelChoiceField(
queryset=WirelessLANGroup.objects.all(),
required=False
)
slug = SlugField()
class Meta:
model = WirelessLANGroup
fields = [
'parent', 'name', 'slug', 'description',
]
class WirelessLANForm(BootstrapMixin, CustomFieldModelForm):
group = DynamicModelChoiceField(
queryset=WirelessLANGroup.objects.all(),
required=False
)
vlan = DynamicModelChoiceField(
queryset=VLAN.objects.all(),
required=False
@ -24,10 +45,10 @@ class WirelessLANForm(BootstrapMixin, CustomFieldModelForm):
class Meta:
model = WirelessLAN
fields = [
'ssid', 'description', 'vlan', 'tags',
'ssid', 'group', 'description', 'vlan', 'tags',
]
fieldsets = (
('Wireless LAN', ('ssid', 'description', 'tags')),
('Wireless LAN', ('ssid', 'group', 'description', 'tags')),
('VLAN', ('vlan',)),
)

View File

@ -7,3 +7,9 @@ from .types import *
class WirelessQuery(graphene.ObjectType):
wirelesslan = ObjectField(WirelessLANType)
wirelesslan_list = ObjectListField(WirelessLANType)
wirelesslangroup = ObjectField(WirelessLANGroupType)
wirelesslangroup_list = ObjectListField(WirelessLANGroupType)
wirelesslink = ObjectField(WirelessLinkType)
wirelesslink_list = ObjectListField(WirelessLinkType)

View File

@ -3,10 +3,19 @@ from netbox.graphql.types import ObjectType
__all__ = (
'WirelessLANType',
'WirelessLANGroupType',
'WirelessLinkType',
)
class WirelessLANGroupType(ObjectType):
class Meta:
model = models.WirelessLANGroup
fields = '__all__'
filterset_class = filtersets.WirelessLANGroupFilterSet
class WirelessLANType(ObjectType):
class Meta:

View File

@ -1,8 +1,7 @@
# 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 mptt.fields
import taggit.managers
@ -17,6 +16,27 @@ class Migration(migrations.Migration):
]
operations = [
migrations.CreateModel(
name='WirelessLANGroup',
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=100, unique=True)),
('slug', models.SlugField(max_length=100, unique=True)),
('description', models.CharField(blank=True, max_length=200)),
('lft', models.PositiveIntegerField(editable=False)),
('rght', models.PositiveIntegerField(editable=False)),
('tree_id', models.PositiveIntegerField(db_index=True, editable=False)),
('level', models.PositiveIntegerField(editable=False)),
('parent', mptt.fields.TreeForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='children', to='wireless.wirelesslangroup')),
],
options={
'ordering': ('name', 'pk'),
'unique_together': {('parent', 'name')},
},
),
migrations.CreateModel(
name='WirelessLAN',
fields=[
@ -25,6 +45,7 @@ class Migration(migrations.Migration):
('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)),
('group', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='wireless_lans', to='wireless.wirelesslangroup')),
('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')),

View File

@ -1,20 +1,58 @@
from django.core.exceptions import ValidationError
from django.db import models
from django.urls import reverse
from mptt.models import MPTTModel, TreeForeignKey
from dcim.choices import LinkStatusChoices
from dcim.constants import WIRELESS_IFACE_TYPES
from extras.utils import extras_features
from netbox.models import BigIDModel, PrimaryModel
from netbox.models import BigIDModel, NestedGroupModel, PrimaryModel
from utilities.querysets import RestrictedQuerySet
from .constants import SSID_MAX_LENGTH
__all__ = (
'WirelessLAN',
'WirelessLANGroup',
'WirelessLink',
)
@extras_features('custom_fields', 'custom_links', 'export_templates', 'webhooks')
class WirelessLANGroup(NestedGroupModel):
"""
A nested grouping of WirelessLANs
"""
name = models.CharField(
max_length=100,
unique=True
)
slug = models.SlugField(
max_length=100,
unique=True
)
parent = TreeForeignKey(
to='self',
on_delete=models.CASCADE,
related_name='children',
blank=True,
null=True,
db_index=True
)
description = models.CharField(
max_length=200,
blank=True
)
class Meta:
ordering = ('name', 'pk')
unique_together = (
('parent', 'name')
)
def get_absolute_url(self):
return reverse('wireless:wirelesslangroup', args=[self.pk])
@extras_features('custom_fields', 'custom_links', 'export_templates', 'tags', 'webhooks')
class WirelessLAN(PrimaryModel):
"""
@ -24,6 +62,13 @@ class WirelessLAN(PrimaryModel):
max_length=SSID_MAX_LENGTH,
verbose_name='SSID'
)
group = models.ForeignKey(
to='wireless.WirelessLANGroup',
on_delete=models.SET_NULL,
related_name='wireless_lans',
blank=True,
null=True
)
vlan = models.ForeignKey(
to='ipam.VLAN',
on_delete=models.PROTECT,
@ -100,7 +145,7 @@ class WirelessLink(PrimaryModel):
objects = RestrictedQuerySet.as_manager()
clone_fields = ('ssid', 'status')
clone_fields = ('ssid', 'group', 'status')
class Meta:
ordering = ['pk']

View File

@ -1,14 +1,35 @@
import django_tables2 as tables
from utilities.tables import (
BaseTable, ButtonsColumn, ChoiceFieldColumn, LinkedCountColumn, MPTTColumn, TagColumn, ToggleColumn,
)
from .models import *
from utilities.tables import BaseTable, ChoiceFieldColumn, TagColumn, ToggleColumn
__all__ = (
'WirelessLANTable',
'WirelessLANGroupTable',
'WirelessLinkTable',
)
class WirelessLANGroupTable(BaseTable):
pk = ToggleColumn()
name = MPTTColumn(
linkify=True
)
wirelesslan_count = LinkedCountColumn(
viewname='wireless:wirelesslan_list',
url_params={'group_id': 'pk'},
verbose_name='Wireless LANs'
)
actions = ButtonsColumn(WirelessLANGroup)
class Meta(BaseTable.Meta):
model = WirelessLANGroup
fields = ('pk', 'name', 'wirelesslan_count', 'description', 'slug', 'actions')
default_columns = ('pk', 'name', 'wirelesslan_count', 'description', 'actions')
class WirelessLANTable(BaseTable):
pk = ToggleColumn()
ssid = tables.Column(

View File

@ -7,6 +7,17 @@ from .models import *
app_name = 'wireless'
urlpatterns = (
# Wireless LAN groups
path('wireless-lan-groups/', views.WirelessLANGroupListView.as_view(), name='wirelesslangroup_list'),
path('wireless-lan-groups/add/', views.WirelessLANGroupEditView.as_view(), name='wirelesslangroup_add'),
path('wireless-lan-groups/import/', views.WirelessLANGroupBulkImportView.as_view(), name='wirelesslangroup_import'),
path('wireless-lan-groups/edit/', views.WirelessLANGroupBulkEditView.as_view(), name='wirelesslangroup_bulk_edit'),
path('wireless-lan-groups/delete/', views.WirelessLANGroupBulkDeleteView.as_view(), name='wirelesslangroup_bulk_delete'),
path('wireless-lan-groups/<int:pk>/', views.WirelessLANGroupView.as_view(), name='wirelesslangroup'),
path('wireless-lan-groups/<int:pk>/edit/', views.WirelessLANGroupEditView.as_view(), name='wirelesslangroup_edit'),
path('wireless-lan-groups/<int:pk>/delete/', views.WirelessLANGroupDeleteView.as_view(), name='wirelesslangroup_delete'),
path('wireless-lan-groups/<int:pk>/changelog/', ObjectChangeLogView.as_view(), name='wirelesslangroup_changelog', kwargs={'model': WirelessLANGroup}),
# Wireless LANs
path('wireless-lans/', views.WirelessLANListView.as_view(), name='wirelesslan_list'),
path('wireless-lans/add/', views.WirelessLANEditView.as_view(), name='wirelesslan_add'),

View File

@ -1,8 +1,81 @@
from netbox.views import generic
from utilities.tables import paginate_table
from . import filtersets, forms, tables
from .models import *
#
# Wireless LAN groups
#
class WirelessLANGroupListView(generic.ObjectListView):
queryset = WirelessLANGroup.objects.add_related_count(
WirelessLANGroup.objects.all(),
WirelessLAN,
'group',
'wirelesslan_count',
cumulative=True
)
filterset = filtersets.WirelessLANGroupFilterSet
filterset_form = forms.WirelessLANGroupFilterForm
table = tables.WirelessLANGroupTable
class WirelessLANGroupView(generic.ObjectView):
queryset = WirelessLANGroup.objects.all()
def get_extra_context(self, request, instance):
wirelesslans = WirelessLAN.objects.restrict(request.user, 'view').filter(
group=instance
)
wirelesslans_table = tables.WirelessLANTable(wirelesslans, exclude=('group',))
paginate_table(wirelesslans_table, request)
return {
'wirelesslans_table': wirelesslans_table,
}
class WirelessLANGroupEditView(generic.ObjectEditView):
queryset = WirelessLANGroup.objects.all()
model_form = forms.WirelessLANGroupForm
class WirelessLANGroupDeleteView(generic.ObjectDeleteView):
queryset = WirelessLANGroup.objects.all()
class WirelessLANGroupBulkImportView(generic.BulkImportView):
queryset = WirelessLANGroup.objects.all()
model_form = forms.WirelessLANGroupCSVForm
table = tables.WirelessLANGroupTable
class WirelessLANGroupBulkEditView(generic.BulkEditView):
queryset = WirelessLANGroup.objects.add_related_count(
WirelessLANGroup.objects.all(),
WirelessLAN,
'group',
'wirelesslan_count',
cumulative=True
)
filterset = filtersets.WirelessLANGroupFilterSet
table = tables.WirelessLANGroupTable
form = forms.WirelessLANGroupBulkEditForm
class WirelessLANGroupBulkDeleteView(generic.BulkDeleteView):
queryset = WirelessLANGroup.objects.add_related_count(
WirelessLANGroup.objects.all(),
WirelessLAN,
'group',
'wirelesslan_count',
cumulative=True
)
filterset = filtersets.WirelessLANGroupFilterSet
table = tables.WirelessLANGroupTable
#
# Wireless LANs
#