Skip to content

Commit

Permalink
Fixes #377: Non-devices do not save coordinates (#387)
Browse files Browse the repository at this point in the history
* corrected nodeId handling

* add models

* import models, migrate db

* correct migration deps

* load coordinates from db

* add serializers, correct imports

* add views and templates

* fix X-Y swap in help text

* add non-devices to coordinate group view
  • Loading branch information
dreng committed Sep 27, 2023
1 parent dbe331a commit 987c7ef
Show file tree
Hide file tree
Showing 26 changed files with 1,404 additions and 33 deletions.
17 changes: 16 additions & 1 deletion netbox_topology_views/api/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
from rest_framework.serializers import ModelSerializer
from netbox.api.serializers import NetBoxModelSerializer

from netbox_topology_views.models import RoleImage, IndividualOptions, CoordinateGroup, Coordinate
from netbox_topology_views.models import RoleImage, IndividualOptions, CoordinateGroup, Coordinate, CircuitCoordinate, PowerPanelCoordinate, PowerFeedCoordinate


class TopologyDummySerializer(ModelSerializer):
Expand Down Expand Up @@ -32,6 +32,21 @@ class Meta:
model = Coordinate
fields = ("x", "y")

class CircuitCoordinateSerializer(NetBoxModelSerializer):
class Meta:
model = CircuitCoordinate
fields = ("x", "y")

class PowerPanelCoordinateSerializer(NetBoxModelSerializer):
class Meta:
model = PowerPanelCoordinate
fields = ("x", "y")

class PowerFeedCoordinateSerializer(NetBoxModelSerializer):
class Meta:
model = PowerFeedCoordinate
fields = ("x", "y")

class IndividualOptionsSerializer(NetBoxModelSerializer):
class Meta:
model = IndividualOptions
Expand Down
17 changes: 12 additions & 5 deletions netbox_topology_views/api/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@
RoleImageSerializer,
TopologyDummySerializer,
)
from netbox_topology_views.models import RoleImage, IndividualOptions, CoordinateGroup, Coordinate
import netbox_topology_views.models
from netbox_topology_views.models import RoleImage, IndividualOptions, CoordinateGroup, Coordinate, CircuitCoordinate, PowerPanelCoordinate, PowerFeedCoordinate
from netbox_topology_views.views import get_topology_data
from netbox_topology_views.utils import get_image_from_url, export_data_to_xml, get_query_settings
from netbox_topology_views.filters import DeviceFilterSet
Expand All @@ -41,20 +42,26 @@ def save_coords(self, request):
if device_id.startswith("c"):
device_id = device_id.lstrip("c")
actual_device = Circuit.objects.get(id=device_id)
model_name = 'CircuitCoordinate'
elif device_id.startswith("p"):
device_id = device_id.lstrip("p")
actual_device = PowerPanel.objects.get(id=device_id)
model_name = 'PowerPanelCoordinate'
elif device_id.startswith("f"):
device_id = device_id.lstrip("f")
actual_device = PowerFeed.objects.get(id=device_id)
model_name = 'PowerFeedCoordinate'
elif device_id.isnumeric():
actual_device = Device.objects.get(id=device_id)
model_name = 'Coordinate'

if not actual_device:
return Response({"status": "invalid node_id in body"}, status=400)

model_class = getattr(netbox_topology_views.models, model_name)

if group_id is None or group_id == "default":
group_id = Coordinate.get_or_create_default_group(group_id)
group_id = model_class.get_or_create_default_group(group_id)
if not group_id:
return Response(
{"status": "Error while creating default group."}, status=500
Expand All @@ -66,12 +73,12 @@ def save_coords(self, request):
# Hen-and-egg-problem. Thanks, Django! By default, Django updates records that
# already exist and inserts otherwise. This does not work with our
# unique_together key if no pk is given. But: No record, no pk.
if not Coordinate.objects.filter(group=group, device=actual_device):
if not model_class.objects.filter(group=group, device=actual_device):
# Unique group/device pair does not exist. Prepare new data set
coords = Coordinate(group=group, device=actual_device, x=x_coord, y=y_coord)
coords = model_class(group=group, device=actual_device, x=x_coord, y=y_coord)
else:
# Unique group/device pair already exists. Update data
coords = Coordinate(pk=Coordinate.objects.get(group=group, device=actual_device).pk, group=group, device=actual_device, x=x_coord, y=y_coord)
coords = model_class(pk=model_class.objects.get(group=group, device=actual_device).pk, group=group, device=actual_device, x=x_coord, y=y_coord)
coords.save()
except:
return Response(
Expand Down
71 changes: 69 additions & 2 deletions netbox_topology_views/filters.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import django_filters
from circuits.models import Circuit
from dcim.choices import DeviceStatusChoices
from dcim.models import Device, DeviceRole, Location, Rack, Region, Site, SiteGroup, Manufacturer, DeviceType, Platform
from dcim.models import Device, DeviceRole, Location, Rack, Region, Site, SiteGroup, Manufacturer, DeviceType, Platform, PowerPanel, PowerFeed
from django.db.models import Q
from extras.filtersets import LocalConfigContextFilterSet
from extras.models import ConfigTemplate
from netbox.filtersets import NetBoxModelFilterSet
from tenancy.filtersets import TenancyFilterSet, ContactModelFilterSet
from utilities.filters import TreeNodeMultipleChoiceFilter, MultiValueCharFilter, MultiValueMACAddressFilter
from netbox_topology_views.models import CoordinateGroup, Coordinate
from netbox_topology_views.models import CoordinateGroup, Coordinate, CircuitCoordinate, PowerPanelCoordinate, PowerFeedCoordinate

class DeviceFilterSet(NetBoxModelFilterSet, TenancyFilterSet, ContactModelFilterSet, LocalConfigContextFilterSet):
q = django_filters.CharFilter(
Expand Down Expand Up @@ -164,6 +165,72 @@ def _has_oob_ip(self, queryset, name, value):
def _virtual_chassis_member(self, queryset, name, value):
return queryset.exclude(virtual_chassis__isnull=value)

class CircuitCoordinatesFilterSet(NetBoxModelFilterSet):
group = django_filters.ModelMultipleChoiceFilter(
queryset = CoordinateGroup.objects.all(),
)

device = django_filters.ModelMultipleChoiceFilter(
queryset = Circuit.objects.all(),
)

class Meta:
model = CircuitCoordinate
fields = ['id', 'group', 'device', 'x', 'y']

def search(self, queryset, name, value):
"""Perform the filtered search."""
if not value.strip():
return queryset
return queryset.filter(
Q(group__name__icontains=value) |
Q(device__name__icontains=value)
)

class PowerPanelCoordinatesFilterSet(NetBoxModelFilterSet):
group = django_filters.ModelMultipleChoiceFilter(
queryset = CoordinateGroup.objects.all(),
)

device = django_filters.ModelMultipleChoiceFilter(
queryset = PowerPanel.objects.all(),
)

class Meta:
model = PowerPanelCoordinate
fields = ['id', 'group', 'device', 'x', 'y']

def search(self, queryset, name, value):
"""Perform the filtered search."""
if not value.strip():
return queryset
return queryset.filter(
Q(group__name__icontains=value) |
Q(device__name__icontains=value)
)

class PowerFeedCoordinatesFilterSet(NetBoxModelFilterSet):
group = django_filters.ModelMultipleChoiceFilter(
queryset = CoordinateGroup.objects.all(),
)

device = django_filters.ModelMultipleChoiceFilter(
queryset = PowerFeed.objects.all(),
)

class Meta:
model = PowerFeedCoordinate
fields = ['id', 'group', 'device', 'x', 'y']

def search(self, queryset, name, value):
"""Perform the filtered search."""
if not value.strip():
return queryset
return queryset.filter(
Q(group__name__icontains=value) |
Q(device__name__icontains=value)
)

class CoordinatesFilterSet(NetBoxModelFilterSet):
group = django_filters.ModelMultipleChoiceFilter(
queryset = CoordinateGroup.objects.all(),
Expand Down
122 changes: 120 additions & 2 deletions netbox_topology_views/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@

from django.utils.translation import gettext as _

from dcim.models import Device, Site, SiteGroup, Region, DeviceRole, Location, Rack, Manufacturer, DeviceType, Platform
from circuits.models import Circuit
from dcim.models import Device, Site, SiteGroup, Region, DeviceRole, Location, Rack, Manufacturer, DeviceType, Platform, PowerPanel, PowerFeed

from django import forms
from dcim.choices import DeviceStatusChoices, DeviceAirflowChoices
Expand All @@ -21,7 +22,7 @@
DynamicModelMultipleChoiceField
)

from netbox_topology_views.models import IndividualOptions, CoordinateGroup, Coordinate
from netbox_topology_views.models import IndividualOptions, CoordinateGroup, Coordinate, CircuitCoordinate, PowerPanelCoordinate, PowerFeedCoordinate

class DeviceFilterForm(
LocalConfigContextFilterForm,
Expand Down Expand Up @@ -272,6 +273,33 @@ class Meta:
model = CoordinateGroup
fields = ('name', 'description')

class CircuitCoordinatesForm(NetBoxModelForm):
fieldsets = (
('CircuitCoordinate', ('group', 'device', 'x', 'y')),
)

class Meta:
model = CircuitCoordinate
fields = ('group', 'device', 'x', 'y')

class PowerPanelCoordinatesForm(NetBoxModelForm):
fieldsets = (
('PowerPanel', ('group', 'device', 'x', 'y')),
)

class Meta:
model = PowerPanelCoordinate
fields = ('group', 'device', 'x', 'y')

class PowerFeedCoordinatesForm(NetBoxModelForm):
fieldsets = (
('PowerFeedCoordinate', ('group', 'device', 'x', 'y')),
)

class Meta:
model = PowerFeedCoordinate
fields = ('group', 'device', 'x', 'y')

class CoordinatesForm(NetBoxModelForm):
fieldsets = (
('Coordinate', ('group', 'device', 'x', 'y')),
Expand All @@ -281,11 +309,101 @@ class Meta:
model = Coordinate
fields = ('group', 'device', 'x', 'y')

class CircuitCoordinatesImportForm(NetBoxModelImportForm):
class Meta:
model = CircuitCoordinate
fields = ('group', 'device', 'x', 'y')

class PowerPanelCoordinatesImportForm(NetBoxModelImportForm):
class Meta:
model = PowerPanelCoordinate
fields = ('group', 'device', 'x', 'y')

class PowerFeedCoordinatesImportForm(NetBoxModelImportForm):
class Meta:
model = PowerFeedCoordinate
fields = ('group', 'device', 'x', 'y')

class CoordinatesImportForm(NetBoxModelImportForm):
class Meta:
model = Coordinate
fields = ('group', 'device', 'x', 'y')

class CircuitCoordinatesFilterForm(NetBoxModelFilterSetForm):
model = CircuitCoordinate
fieldsets = (
(None, ('q', 'filter_id')),
('CircuitCoordinates', ('group', 'device', 'x', 'y'))
)

group = forms.ModelMultipleChoiceField(
queryset=CoordinateGroup.objects.all(),
required=False
)

device = DynamicModelMultipleChoiceField(
queryset=Circuit.objects.all(),
required=False
)

x = forms.IntegerField(
required=False
)

y = forms.IntegerField(
required=False
)

class PowerPanelCoordinatesFilterForm(NetBoxModelFilterSetForm):
model = PowerPanelCoordinate
fieldsets = (
(None, ('q', 'filter_id')),
('PowerPanelCoordinates', ('group', 'device', 'x', 'y'))
)

group = forms.ModelMultipleChoiceField(
queryset=CoordinateGroup.objects.all(),
required=False
)

device = DynamicModelMultipleChoiceField(
queryset=PowerPanel.objects.all(),
required=False
)

x = forms.IntegerField(
required=False
)

y = forms.IntegerField(
required=False
)

class PowerFeedCoordinatesFilterForm(NetBoxModelFilterSetForm):
model = Coordinate
fieldsets = (
(None, ('q', 'filter_id')),
('PowerFeedCoordinates', ('group', 'device', 'x', 'y'))
)

group = forms.ModelMultipleChoiceField(
queryset=CoordinateGroup.objects.all(),
required=False
)

device = DynamicModelMultipleChoiceField(
queryset=PowerFeed.objects.all(),
required=False
)

x = forms.IntegerField(
required=False
)

y = forms.IntegerField(
required=False
)

class CoordinatesFilterForm(NetBoxModelFilterSetForm):
model = Coordinate
fieldsets = (
Expand Down
Loading

0 comments on commit 987c7ef

Please sign in to comment.