Add status field to WirelessLink

This commit is contained in:
jeremystretch 2021-10-13 14:31:30 -04:00
parent ec0560a2c5
commit 95ed07a95e
21 changed files with 80 additions and 37 deletions

View File

@ -757,7 +757,7 @@ class CableSerializer(PrimaryModelSerializer):
)
termination_a = serializers.SerializerMethodField(read_only=True)
termination_b = serializers.SerializerMethodField(read_only=True)
status = ChoiceField(choices=CableStatusChoices, required=False)
status = ChoiceField(choices=LinkStatusChoices, required=False)
length_unit = ChoiceField(choices=CableLengthUnitChoices, allow_blank=True, required=False)
class Meta:

View File

@ -1030,7 +1030,7 @@ class PortTypeChoices(ChoiceSet):
#
# Cables
# Cables/links
#
class CableTypeChoices(ChoiceSet):
@ -1094,7 +1094,7 @@ class CableTypeChoices(ChoiceSet):
)
class CableStatusChoices(ChoiceSet):
class LinkStatusChoices(ChoiceSet):
STATUS_CONNECTED = 'connected'
STATUS_PLANNED = 'planned'

View File

@ -1205,7 +1205,7 @@ class CableFilterSet(PrimaryModelFilterSet):
choices=CableTypeChoices
)
status = django_filters.MultipleChoiceFilter(
choices=CableStatusChoices
choices=LinkStatusChoices
)
color = django_filters.MultipleChoiceFilter(
choices=ColorChoices

View File

@ -453,7 +453,7 @@ class CableBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldModelBulkE
widget=StaticSelect()
)
status = forms.ChoiceField(
choices=add_blank_choice(CableStatusChoices),
choices=add_blank_choice(LinkStatusChoices),
required=False,
widget=StaticSelect(),
initial=''

View File

@ -807,7 +807,7 @@ class CableCSVForm(CustomFieldModelCSVForm):
# Cable attributes
status = CSVChoiceField(
choices=CableStatusChoices,
choices=LinkStatusChoices,
required=False,
help_text='Connection status'
)

View File

@ -732,7 +732,7 @@ class CableFilterForm(BootstrapMixin, CustomFieldModelFilterForm):
)
status = forms.ChoiceField(
required=False,
choices=add_blank_choice(CableStatusChoices),
choices=add_blank_choice(LinkStatusChoices),
widget=StaticSelect()
)
color = ColorField(

View File

@ -64,8 +64,8 @@ class Cable(PrimaryModel):
)
status = models.CharField(
max_length=50,
choices=CableStatusChoices,
default=CableStatusChoices.STATUS_CONNECTED
choices=LinkStatusChoices,
default=LinkStatusChoices.STATUS_CONNECTED
)
label = models.CharField(
max_length=100,
@ -285,7 +285,7 @@ class Cable(PrimaryModel):
self._pk = self.pk
def get_status_class(self):
return CableStatusChoices.CSS_CLASSES.get(self.status)
return LinkStatusChoices.CSS_CLASSES.get(self.status)
def get_compatible_types(self):
"""
@ -390,7 +390,7 @@ class CablePath(BigIDModel):
node = origin
while node.link is not None:
if hasattr(node.link, 'status') and node.link.status != CableStatusChoices.STATUS_CONNECTED:
if hasattr(node.link, 'status') and node.link.status != LinkStatusChoices.STATUS_CONNECTED:
is_active = False
# Follow the link to its far-end termination

View File

@ -4,7 +4,7 @@ from django.contrib.contenttypes.models import ContentType
from django.db.models.signals import post_save, post_delete, pre_delete
from django.dispatch import receiver
from .choices import CableStatusChoices
from .choices import LinkStatusChoices
from .models import Cable, CablePath, Device, PathEndpoint, PowerPanel, Rack, Location, VirtualChassis
from .utils import create_cablepath, rebuild_paths
@ -102,7 +102,7 @@ def update_connected_endpoints(instance, created, raw=False, **kwargs):
# We currently don't support modifying either termination of an existing Cable. (This
# may change in the future.) However, we do need to capture status changes and update
# any CablePaths accordingly.
if instance.status != CableStatusChoices.STATUS_CONNECTED:
if instance.status != LinkStatusChoices.STATUS_CONNECTED:
CablePath.objects.filter(path__contains=instance).update(is_active=False)
else:
rebuild_paths(instance)

View File

@ -2,7 +2,7 @@ from django.contrib.contenttypes.models import ContentType
from django.test import TestCase
from circuits.models import *
from dcim.choices import CableStatusChoices
from dcim.choices import LinkStatusChoices
from dcim.models import *
from dcim.utils import object_to_path_node
@ -1142,7 +1142,7 @@ class CablePathTestCase(TestCase):
self.assertEqual(CablePath.objects.count(), 2)
# Change cable 2's status to "planned"
cable2.status = CableStatusChoices.STATUS_PLANNED
cable2.status = LinkStatusChoices.STATUS_PLANNED
cable2.save()
self.assertPathExists(
origin=interface1,
@ -1160,7 +1160,7 @@ class CablePathTestCase(TestCase):
# Change cable 2's status to "connected"
cable2 = Cable.objects.get(pk=cable2.pk)
cable2.status = CableStatusChoices.STATUS_CONNECTED
cable2.status = LinkStatusChoices.STATUS_CONNECTED
cable2.save()
self.assertPathExists(
origin=interface1,

View File

@ -2855,12 +2855,12 @@ class CableTestCase(TestCase, ChangeLoggedFilterSetTests):
console_server_port = ConsoleServerPort.objects.create(device=devices[0], name='Console Server Port 1')
# Cables
Cable(termination_a=interfaces[1], termination_b=interfaces[2], label='Cable 1', type=CableTypeChoices.TYPE_CAT3, status=CableStatusChoices.STATUS_CONNECTED, color='aa1409', length=10, length_unit=CableLengthUnitChoices.UNIT_FOOT).save()
Cable(termination_a=interfaces[3], termination_b=interfaces[4], label='Cable 2', type=CableTypeChoices.TYPE_CAT3, status=CableStatusChoices.STATUS_CONNECTED, color='aa1409', length=20, length_unit=CableLengthUnitChoices.UNIT_FOOT).save()
Cable(termination_a=interfaces[5], termination_b=interfaces[6], label='Cable 3', type=CableTypeChoices.TYPE_CAT5E, status=CableStatusChoices.STATUS_CONNECTED, color='f44336', length=30, length_unit=CableLengthUnitChoices.UNIT_FOOT).save()
Cable(termination_a=interfaces[7], termination_b=interfaces[8], label='Cable 4', type=CableTypeChoices.TYPE_CAT5E, status=CableStatusChoices.STATUS_PLANNED, color='f44336', length=40, length_unit=CableLengthUnitChoices.UNIT_FOOT).save()
Cable(termination_a=interfaces[9], termination_b=interfaces[10], label='Cable 5', type=CableTypeChoices.TYPE_CAT6, status=CableStatusChoices.STATUS_PLANNED, color='e91e63', length=10, length_unit=CableLengthUnitChoices.UNIT_METER).save()
Cable(termination_a=interfaces[11], termination_b=interfaces[0], label='Cable 6', type=CableTypeChoices.TYPE_CAT6, status=CableStatusChoices.STATUS_PLANNED, color='e91e63', length=20, length_unit=CableLengthUnitChoices.UNIT_METER).save()
Cable(termination_a=interfaces[1], termination_b=interfaces[2], label='Cable 1', type=CableTypeChoices.TYPE_CAT3, status=LinkStatusChoices.STATUS_CONNECTED, color='aa1409', length=10, length_unit=CableLengthUnitChoices.UNIT_FOOT).save()
Cable(termination_a=interfaces[3], termination_b=interfaces[4], label='Cable 2', type=CableTypeChoices.TYPE_CAT3, status=LinkStatusChoices.STATUS_CONNECTED, color='aa1409', length=20, length_unit=CableLengthUnitChoices.UNIT_FOOT).save()
Cable(termination_a=interfaces[5], termination_b=interfaces[6], label='Cable 3', type=CableTypeChoices.TYPE_CAT5E, status=LinkStatusChoices.STATUS_CONNECTED, color='f44336', length=30, length_unit=CableLengthUnitChoices.UNIT_FOOT).save()
Cable(termination_a=interfaces[7], termination_b=interfaces[8], label='Cable 4', type=CableTypeChoices.TYPE_CAT5E, status=LinkStatusChoices.STATUS_PLANNED, color='f44336', length=40, length_unit=CableLengthUnitChoices.UNIT_FOOT).save()
Cable(termination_a=interfaces[9], termination_b=interfaces[10], label='Cable 5', type=CableTypeChoices.TYPE_CAT6, status=LinkStatusChoices.STATUS_PLANNED, color='e91e63', length=10, length_unit=CableLengthUnitChoices.UNIT_METER).save()
Cable(termination_a=interfaces[11], termination_b=interfaces[0], label='Cable 6', type=CableTypeChoices.TYPE_CAT6, status=LinkStatusChoices.STATUS_PLANNED, color='e91e63', length=20, length_unit=CableLengthUnitChoices.UNIT_METER).save()
Cable(termination_a=console_port, termination_b=console_server_port, label='Cable 7').save()
def test_label(self):
@ -2880,9 +2880,9 @@ class CableTestCase(TestCase, ChangeLoggedFilterSetTests):
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
def test_status(self):
params = {'status': [CableStatusChoices.STATUS_CONNECTED]}
params = {'status': [LinkStatusChoices.STATUS_CONNECTED]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
params = {'status': [CableStatusChoices.STATUS_PLANNED]}
params = {'status': [LinkStatusChoices.STATUS_PLANNED]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 3)
def test_color(self):

View File

@ -1813,7 +1813,7 @@ class CableTestCase(
'termination_b_type': interface_ct.pk,
'termination_b_id': interfaces[3].pk,
'type': CableTypeChoices.TYPE_CAT6,
'status': CableStatusChoices.STATUS_PLANNED,
'status': LinkStatusChoices.STATUS_PLANNED,
'label': 'Label',
'color': 'c0c0c0',
'length': 100,
@ -1830,7 +1830,7 @@ class CableTestCase(
cls.bulk_edit_data = {
'type': CableTypeChoices.TYPE_CAT5E,
'status': CableStatusChoices.STATUS_CONNECTED,
'status': LinkStatusChoices.STATUS_CONNECTED,
'label': 'New label',
'color': '00ff00',
'length': 50,

View File

@ -15,6 +15,10 @@
<h5 class="card-header">Link Properties</h5>
<div class="card-body">
<table class="table table-hover attr-table">
<tr>
<th scope="row">Status</th>
<td>{{ object.get_status_display }}</td>
</tr>
<tr>
<th scope="row">SSID</th>
<td>{{ object.ssid|placeholder }}</td>

View File

@ -1,7 +1,9 @@
from rest_framework import serializers
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 wireless.models import *
from .nested_serializers import *
@ -25,11 +27,12 @@ class WirelessLANSerializer(PrimaryModelSerializer):
class WirelessLinkSerializer(PrimaryModelSerializer):
url = serializers.HyperlinkedIdentityField(view_name='wireless-api:wirelesslink-detail')
status = ChoiceField(choices=LinkStatusChoices, required=False)
interface_a = NestedInterfaceSerializer()
interface_b = NestedInterfaceSerializer()
class Meta:
model = WirelessLink
fields = [
'id', 'url', 'display', 'interface_a', 'interface_b', 'ssid', 'description',
'id', 'url', 'display', 'interface_a', 'interface_b', 'ssid', 'status', 'description',
]

View File

@ -1,6 +1,7 @@
import django_filters
from django.db.models import Q
from dcim.choices import LinkStatusChoices
from extras.filters import TagFilter
from netbox.filtersets import PrimaryModelFilterSet
from .models import *
@ -37,6 +38,9 @@ class WirelessLinkFilterSet(PrimaryModelFilterSet):
method='search',
label='Search',
)
status = django_filters.MultipleChoiceFilter(
choices=LinkStatusChoices
)
tag = TagFilter()
class Meta:

View File

@ -1,10 +1,11 @@
from django import forms
from dcim.models import *
from dcim.choices import LinkStatusChoices
from extras.forms import AddRemoveTagsForm, CustomFieldModelBulkEditForm
from ipam.models import VLAN
from utilities.forms import BootstrapMixin, DynamicModelChoiceField
from wireless.constants import SSID_MAX_LENGTH
from wireless.models import *
__all__ = (
'WirelessLANBulkEditForm',
@ -14,7 +15,7 @@ __all__ = (
class WirelessLANBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldModelBulkEditForm):
pk = forms.ModelMultipleChoiceField(
queryset=PowerFeed.objects.all(),
queryset=WirelessLAN.objects.all(),
widget=forms.MultipleHiddenInput
)
vlan = DynamicModelChoiceField(
@ -35,13 +36,17 @@ class WirelessLANBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldMode
class WirelessLinkBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldModelBulkEditForm):
pk = forms.ModelMultipleChoiceField(
queryset=PowerFeed.objects.all(),
queryset=WirelessLink.objects.all(),
widget=forms.MultipleHiddenInput
)
ssid = forms.CharField(
max_length=SSID_MAX_LENGTH,
required=False
)
status = forms.ChoiceField(
choices=LinkStatusChoices,
required=False
)
description = forms.CharField(
required=False
)

View File

@ -1,7 +1,8 @@
from dcim.choices import LinkStatusChoices
from dcim.models import Interface
from extras.forms import CustomFieldModelCSVForm
from ipam.models import VLAN
from utilities.forms import CSVModelChoiceField
from utilities.forms import CSVChoiceField, CSVModelChoiceField
from wireless.models import *
__all__ = (
@ -23,6 +24,10 @@ class WirelessLANCSVForm(CustomFieldModelCSVForm):
class WirelessLinkCSVForm(CustomFieldModelCSVForm):
status = CSVChoiceField(
choices=LinkStatusChoices,
help_text='Connection status'
)
interface_a = CSVModelChoiceField(
queryset=Interface.objects.all()
)

View File

@ -1,8 +1,9 @@
from django import forms
from django.utils.translation import gettext as _
from dcim.choices import LinkStatusChoices
from extras.forms import CustomFieldModelFilterForm
from utilities.forms import BootstrapMixin, TagFilterField
from utilities.forms import add_blank_choice, BootstrapMixin, StaticSelect, TagFilterField
from wireless.models import *
__all__ = (
@ -42,4 +43,9 @@ class WirelessLinkFilterForm(BootstrapMixin, CustomFieldModelFilterForm):
required=False,
label='SSID'
)
status = forms.ChoiceField(
required=False,
choices=add_blank_choice(LinkStatusChoices),
widget=StaticSelect()
)
tag = TagFilterField(model)

View File

@ -2,7 +2,7 @@ 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 utilities.forms import BootstrapMixin, DynamicModelChoiceField, DynamicModelMultipleChoiceField, StaticSelect
from wireless.models import *
__all__ = (
@ -55,5 +55,8 @@ class WirelessLinkForm(BootstrapMixin, CustomFieldModelForm):
class Meta:
model = WirelessLink
fields = [
'interface_a', 'interface_b', 'ssid', 'description', 'tags',
'interface_a', 'interface_b', 'status', 'ssid', 'description', 'tags',
]
widgets = {
'status': StaticSelect,
}

View File

@ -42,6 +42,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(blank=True, max_length=32)),
('status', models.CharField(default='connected', max_length=50)),
('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')),

View File

@ -2,6 +2,7 @@ from django.core.exceptions import ValidationError
from django.db import models
from django.urls import reverse
from dcim.choices import LinkStatusChoices
from dcim.constants import WIRELESS_IFACE_TYPES
from extras.utils import extras_features
from netbox.models import BigIDModel, PrimaryModel
@ -70,6 +71,11 @@ class WirelessLink(PrimaryModel):
blank=True,
verbose_name='SSID'
)
status = models.CharField(
max_length=50,
choices=LinkStatusChoices,
default=LinkStatusChoices.STATUS_CONNECTED
)
description = models.CharField(
max_length=200,
blank=True
@ -94,6 +100,8 @@ class WirelessLink(PrimaryModel):
objects = RestrictedQuerySet.as_manager()
clone_fields = ('ssid', 'status')
class Meta:
ordering = ['pk']
unique_together = ('interface_a', 'interface_b')
@ -104,6 +112,9 @@ class WirelessLink(PrimaryModel):
def get_absolute_url(self):
return reverse('wireless:wirelesslink', args=[self.pk])
def get_status_class(self):
return LinkStatusChoices.CSS_CLASSES.get(self.status)
def clean(self):
# Validate interface types

View File

@ -1,7 +1,7 @@
import django_tables2 as tables
from .models import *
from utilities.tables import BaseTable, TagColumn, ToggleColumn
from utilities.tables import BaseTable, ChoiceFieldColumn, TagColumn, ToggleColumn
__all__ = (
'WirelessLANTable',
@ -30,6 +30,7 @@ class WirelessLinkTable(BaseTable):
linkify=True,
verbose_name='ID'
)
status = ChoiceFieldColumn()
interface_a = tables.Column(
linkify=True
)
@ -42,5 +43,5 @@ class WirelessLinkTable(BaseTable):
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')
fields = ('pk', 'id', 'status', 'interface_a', 'interface_b', 'ssid', 'description')
default_columns = ('pk', 'id', 'status', 'interface_a', 'interface_b', 'ssid', 'description')