Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Option to disable Physics #489

Open
wants to merge 19 commits into
base: develop
Choose a base branch
from
2 changes: 1 addition & 1 deletion netbox_topology_views/api/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,4 +50,4 @@ class Meta:
class IndividualOptionsSerializer(NetBoxModelSerializer):
class Meta:
model = IndividualOptions
fields = ("ignore_cable_type", "save_coords", "show_unconnected", "show_cables", "show_logical_connections", "show_single_cable_logical_conns", "show_neighbors", "show_circuit", "show_power", "show_wireless", "group_sites", "group_locations", "group_racks", "draw_default_layout")
fields = ("ignore_cable_type", "save_coords", "show_unconnected", "show_cables", "show_logical_connections", "show_single_cable_logical_conns", "show_neighbors", "show_circuit", "show_power", "show_wireless", "group_sites", "group_locations", "group_racks", "draw_default_layout", "disable_physics",)
4 changes: 3 additions & 1 deletion netbox_topology_views/api/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ def list(self, request):

if request.GET:

filter_id, save_coords, show_unconnected, show_power, show_circuit, show_logical_connections, show_single_cable_logical_conns, show_cables, show_wireless, group_sites, group_locations, group_racks,show_neighbors = get_query_settings(request)
filter_id, save_coords, show_unconnected, show_power, show_circuit, show_logical_connections, show_single_cable_logical_conns, show_cables, show_wireless, group_sites, group_locations, group_racks, show_neighbors, disable_physics = get_query_settings(request)

# Read options from saved filters as NetBox does not handle custom plugin filters
if "filter_id" in request.GET and request.GET["filter_id"] != '':
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

get_query_settings() has to be expanded with the new option(s).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed, Github cannot figure it out because reasons.

Expand All @@ -128,6 +128,7 @@ def list(self, request):
if group_locations == False and 'group_locations' in saved_filter_params: group_locations = saved_filter_params['group_locations']
if group_racks == False and 'group_racks' in saved_filter_params: group_racks = saved_filter_params['group_racks']
if show_neighbors == False and 'show_neighbors' in saved_filter_params: show_neighbors = saved_filter_params['show_neighbors']
if disable_physics == False and 'disable_physics' in saved_filter_params: disable_physics = saved_filter_params['disable_physics']
except SavedFilter.DoesNotExist: # filter_id not found
pass
except Exception as inst:
Expand All @@ -153,6 +154,7 @@ def list(self, request):
group_locations=group_locations,
group_racks=group_racks,
group_id=group_id,
disable_physics=disable_physics,
)
xml_data = export_data_to_xml(topo_data).decode('utf-8')

Expand Down
18 changes: 15 additions & 3 deletions netbox_topology_views/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ class DeviceFilterForm(
FieldSet(
'group', 'save_coords', 'show_unconnected', 'show_cables', 'show_logical_connections',
'show_single_cable_logical_conns', 'show_neighbors', 'show_circuit', 'show_power', 'show_wireless',
'group_sites', 'group_locations', 'group_racks', name=_("Options")
'group_sites', 'group_locations', 'group_racks', 'disable_physics', name=_("Options")
),
FieldSet('id', name=_("Device")),
FieldSet('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id', name=_("Location")),
Expand Down Expand Up @@ -240,7 +240,7 @@ class DeviceFilterForm(
)
show_unconnected = forms.NullBooleanField(
label=_('Show Unconnected'),
required=False,
required=False,
initial=False,
widget=forms.Select(
choices=BOOLEAN_WITH_BLANK_CHOICES
Expand Down Expand Up @@ -310,6 +310,9 @@ class DeviceFilterForm(
choices=BOOLEAN_WITH_BLANK_CHOICES
)
)
disable_physics = forms.BooleanField(
label =_('Disable Physics'), required=False, initial=False
)

class CoordinateGroupsForm(NetBoxModelForm):
fieldsets = (
Expand Down Expand Up @@ -502,6 +505,7 @@ class IndividualOptionsForm(NetBoxModelForm):
'group_locations',
'group_racks',
'draw_default_layout',
'disable_physics',
dremerb marked this conversation as resolved.
Show resolved Hide resolved
),
)

Expand Down Expand Up @@ -629,12 +633,20 @@ class IndividualOptionsForm(NetBoxModelForm):
help_text=_('Enable this option if you want to draw the topology on '
'the initial load (when you go to the topology plugin page).')
)
disable_physics = forms.BooleanField(
label=('Disable Physics'),
required=False,
initial=False,
help_text=_('When enables, no forces will act on nodes in the topology and they will only move '
'when dragged by hand. Devices without coordinates will be placed at (0, 0) by default.')
)

class Meta:
model = IndividualOptions
fields = [
'user_id', 'ignore_cable_type', 'preselected_device_roles', 'preselected_tags',
'save_coords', 'show_unconnected', 'show_cables', 'show_logical_connections',
'show_single_cable_logical_conns', 'show_neighbors', 'show_circuit', 'show_power',
'show_wireless', 'group_sites', 'group_locations', 'group_racks', 'draw_default_layout'
'show_wireless', 'group_sites', 'group_locations', 'group_racks', 'draw_default_layout',
'disable_physics'
]
18 changes: 18 additions & 0 deletions netbox_topology_views/migrations/0009_disable_physics.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Generated by Django 4.2.10 on 2024-04-29 14:37

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('netbox_topology_views', '0007_individualoptions_group_locations_and_more'),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is problematic, because another PR lists this 0007 as a dependency. This one is no. 0009 and should depend on 0008.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indeed, this happened because I was working on this PR and #488 in parallel. #488 contains a migration with index 0008, therefore this got 0009 and cannot depend on 0008, since it is on another branch.

I propose leaving this comment open until either #488 or this PR gets accepted and change it accordingly as a last step.

]

operations = [
migrations.AddField(
model_name='individualoptions',
name='disable_physics',
field=models.BooleanField(default=False),
),
]
3 changes: 3 additions & 0 deletions netbox_topology_views/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -398,6 +398,9 @@ class IndividualOptions(NetBoxModel):
draw_default_layout = models.BooleanField(
default=False
)
disable_physics = models.BooleanField(
default=False
)

_netbox_private = True

Expand Down
9 changes: 7 additions & 2 deletions netbox_topology_views/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -177,8 +177,13 @@ def get_query_settings(request):
if "show_neighbors" in request.GET:
if request.GET["show_neighbors"] == "True" :
show_neighbors = True

return filter_id, save_coords, show_unconnected, show_power, show_circuit, show_logical_connections, show_single_cable_logical_conns, show_cables, show_wireless, group_sites, group_locations, group_racks, show_neighbors

dremerb marked this conversation as resolved.
Show resolved Hide resolved
disable_physics = False
if "disable_physics" in request.GET:
if request.GET["disable_physics"] == "on":
disable_physics = True

return filter_id, save_coords, show_unconnected, show_power, show_circuit, show_logical_connections, show_single_cable_logical_conns, show_cables, show_wireless, group_sites, group_locations, group_racks, show_neighbors, disable_physics

class LinePattern():
wireless = [2, 10, 2, 10]
Expand Down
60 changes: 49 additions & 11 deletions netbox_topology_views/views.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import json
from functools import reduce
import random
from typing import DefaultDict, Dict, Optional, Union
import time
from itertools import chain
Expand Down Expand Up @@ -97,7 +98,7 @@ def get_image_for_entity(entity: Union[Device, Circuit, PowerPanel, PowerFeed]):


def create_node(
device: Union[Device, Circuit, PowerPanel, PowerFeed], save_coords: bool, group_id="default"
device: Union[Device, Circuit, PowerPanel, PowerFeed], save_coords: bool, disable_physics: bool, group_id="default", nnodes=1,
):
node = {}
node_content = ""
Expand Down Expand Up @@ -200,11 +201,12 @@ def create_node(
if not group_id:
print('Exception occured while handling default group.')
return node

group = get_object_or_404(CoordinateGroup, pk=group_id)

node["physics"] = True
# Coords must be set even if no coords have been stored. Otherwise nodes with coords
node["physics"] = not disable_physics

# Coords must be set even if no coords have been stored. Otherwise nodes with coords
# will not be placed correctly by vis-network.
node["x"] = 0
node["y"] = 0
Expand All @@ -222,6 +224,21 @@ def create_node(
node["x"] = int(cords[0])
node["y"] = int(cords[1])
node["physics"] = False
elif disable_physics:
# draw devices clustered by their rack neighbours
# for center coordinates of a rack, use the rack name as seed (if available)
random.seed(device.rack.name if hasattr(device, "rack") else device.name)
# set the upper size of the graph
plot_size = int(nnodes / 12) + 1
base_coords_x = random.randint(-plot_size * nnodes, plot_size * nnodes)
base_coords_y = random.randint(-plot_size * nnodes, plot_size * nnodes)
# make seed unique for devices for spread
if hasattr(device, "rack"):
random.seed(device.name)
# spread devices around the rack center coordinates
node["x"] = base_coords_x + random.randint(-2 * nnodes, 2 * nnodes)
node["y"] = base_coords_y + random.randint(-2 * nnodes, 2 * nnodes)


dev_title = "<table><tbody> %s</tbody></table>" % (node_content)
node["title"] = dev_title
Expand All @@ -238,6 +255,7 @@ def create_edge(
edge_id: int,
termination_a: Dict,
termination_b: Dict,
disable_physics: bool,
circuit: Optional[Dict] = None,
cable: Optional[Cable] = None,
wireless: Optional[Dict] = None,
Expand Down Expand Up @@ -300,6 +318,9 @@ def create_edge(
if hasattr(cable, 'color') and cable.color != "":
edge["color"] = "#" + cable.color

# Invert, because value must be False if disabled
edge["physics"] = not disable_physics

return edge


Expand Down Expand Up @@ -339,6 +360,7 @@ def get_topology_data(
group_locations: bool,
group_racks: bool,
group_id,
disable_physics: bool,
):

supported_termination_types = []
Expand Down Expand Up @@ -436,6 +458,7 @@ def get_topology_data(
circuit=circuit_model,
termination_a=termination_a,
termination_b=termination_b,
disable_physics=disable_physics,
)
)

Expand All @@ -462,7 +485,7 @@ def get_topology_data(
] = circuit_termination.circuit

for d in nodes_circuits.values():
nodes.append(create_node(d, save_coords, group_id))
nodes.append(create_node(d, save_coords, disable_physics, group_id))

if show_power:
power_panels_ids = PowerPanel.objects.filter(
Expand Down Expand Up @@ -505,17 +528,18 @@ def get_topology_data(
termination_a=termination_a,
termination_b=termination_b,
power=True,
disable_physics=disable_physics,
)
)

if power_feed.cable_id is not None:
cable_ids[power_feed.cable_id][power_feed.cable_end] = termination_b

for d in nodes_powerfeed.values():
nodes.append(create_node(d, save_coords, group_id))
nodes.append(create_node(d, save_coords, disable_physics, group_id))

for d in nodes_powerpanel.values():
nodes.append(create_node(d, save_coords, group_id))
nodes.append(create_node(d, save_coords, disable_physics, group_id))

if show_logical_connections:
interfaces = Interface.objects.filter(
Expand Down Expand Up @@ -543,7 +567,15 @@ def get_topology_data(
edge_ids += 1
termination_a = { "termination_name": interface.name, "termination_device_name": interface.device.name, "device_id": interface.device.id }
termination_b = { "termination_name": destination.name, "termination_device_name": destination.device.name, "device_id": destination.device.id }
edges.append(create_edge(edge_id=edge_ids, termination_a=termination_a, termination_b=termination_b, interface=interface))
edges.append(
create_edge(
edge_id=edge_ids,
termination_a=termination_a,
termination_b=termination_b,
interface=interface,
disable_physics=disable_physics
)
)
nodes_devices[interface.device.id] = interface.device
nodes_devices[destination.device.id] = destination.device

Expand Down Expand Up @@ -623,6 +655,7 @@ def get_topology_data(
cable=link.cable,
termination_a=termination_a,
termination_b=termination_b,
disable_physics=disable_physics,
)
)

Expand Down Expand Up @@ -662,6 +695,7 @@ def get_topology_data(
termination_a=termination_a,
termination_b=termination_b,
wireless=wireless,
disable_physics=disable_physics
)
)

Expand All @@ -679,7 +713,7 @@ def get_topology_data(
results = {}

for d in nodes_devices.values():
nodes.append(create_node(d, save_coords, group_id))
nodes.append(create_node(d, save_coords, disable_physics, group_id, nnodes=len(nodes_devices.keys())))

results["nodes"] = nodes
results["edges"] = edges
Expand Down Expand Up @@ -710,7 +744,7 @@ def get(self, request):

if request.GET:

filter_id, save_coords, show_unconnected, show_power, show_circuit, show_logical_connections, show_single_cable_logical_conns, show_cables, show_wireless, group_sites, group_locations, group_racks, show_neighbors = get_query_settings(request)
filter_id, save_coords, show_unconnected, show_power, show_circuit, show_logical_connections, show_single_cable_logical_conns, show_cables, show_wireless, group_sites, group_locations, group_racks, show_neighbors, disable_physics = get_query_settings(request)

# Read options from saved filters as NetBox does not handle custom plugin filters
if "filter_id" in request.GET and request.GET["filter_id"] != '':
Expand All @@ -729,6 +763,7 @@ def get(self, request):
if group_locations == False and 'group_locations' in saved_filter_params: group_locations = saved_filter_params['group_locations']
if group_racks == False and 'group_racks' in saved_filter_params: group_racks = saved_filter_params['group_racks']
if show_neighbors == False and 'show_neighbors' in saved_filter_params: show_neighbors = saved_filter_params['show_neighbors']
if disable_physics == False and 'disable_physics' in saved_filter_params: disable_physics = saved_filter_params['disable_physics']
except SavedFilter.DoesNotExist: # filter_id not found
pass
except Exception as inst:
Expand Down Expand Up @@ -756,6 +791,7 @@ def get(self, request):
group_locations=group_locations,
group_racks=group_racks,
group_id=group_id,
disable_physics=disable_physics,
)

else:
Expand All @@ -779,7 +815,8 @@ def get(self, request):
if individualOptions.group_sites: q['group_sites'] = "True"
if individualOptions.group_locations: q['group_locations'] = "True"
if individualOptions.group_racks: q['group_racks'] = "True"
if individualOptions.draw_default_layout:
if individualOptions.disable_physics: q['disable_physics'] = "True"
if individualOptions.draw_default_layout:
q['draw_init'] = "True"
else:
q['draw_init'] = "False"
Expand Down Expand Up @@ -1137,6 +1174,7 @@ def get(self, request):
'group_locations': queryset.group_locations,
'group_racks': queryset.group_racks,
'draw_default_layout': queryset.draw_default_layout,
'disable_physics': queryset.disable_physics,
},
)

Expand Down