rework load/save of coords; add group coords save

This commit is contained in:
dreng
2023-05-06 23:01:08 +02:00
parent 9cc1607811
commit dbf2d23905
7 changed files with 103 additions and 82 deletions
+43 -36
View File
@@ -33,6 +33,7 @@ class SaveCoordsViewSet(ReadOnlyModelViewSet):
device_id: str = request.data.get("node_id", None)
x_coord = request.data.get("x", None)
y_coord = request.data.get("y", None)
group_id = request.data.get("group", "None")
actual_device = None
if device_id.startswith("c"):
@@ -50,53 +51,59 @@ class SaveCoordsViewSet(ReadOnlyModelViewSet):
if not actual_device:
return Response({"status": "invalid node_id in body"}, status=400)
# Storing coordinates in custom field is deprecated now.
# We preserve this for backwards compatibility.
try:
actual_device.custom_field_data["coordinates"] = "%s;%s" % (
x_coord,
y_coord,
)
actual_device.save()
except:
return Response(
{"status": "coords custom field could not be saved"}, status=500
)
# Default group named "default" must always exist in order to make sure
# that coordinate values can be stored even if no coordinate group has been
# selected. The default group will be added automatically if it does not exist.
try:
group = CoordinateGroup.objects.get(name="default")
except CoordinateGroup.DoesNotExist:
if group_id is None or group_id == "default":
# Storing coordinates in custom field is deprecated now.
# We preserve this for backwards compatibility.
try:
group = CoordinateGroup(
name="default",
description="Automatically generated default group. If you delete "
"this group, all default coordinates are gone for good but "
"the group itself will be re-created."
actual_device.custom_field_data["coordinates"] = "%s;%s" % (
x_coord,
y_coord,
)
group.save()
actual_device.save()
except:
return Response(
{"status": "coords custom field could not be saved"}, status=500
)
# Default group named "default" must exist in order to make sure that
# coordinate values can be stored even if no coordinate group has been
# selected. The default group will be added automatically if it does not exist.
try:
if CoordinateGroup.objects.filter(name="default"):
group = CoordinateGroup.objects.get(name="default")
group_id = group.pk
else:
group = CoordinateGroup(
name="default",
description="Automatically generated default group. If you delete "
"this group, all default coordinates are gone for good but "
"the group itself will be re-created."
)
group.save()
group_id = group.pk
except:
return Response(
{"status": "Error while creating default group."}, status=500
)
)
try:
# 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):
# Unique group/device pair does not exist. Prepare new data set
coords = Coordinate(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.save()
if CoordinateGroup.objects.filter(pk=group_id):
group = CoordinateGroup.objects.get(pk=group_id)
# 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):
# Unique group/device pair does not exist. Prepare new data set
coords = Coordinate(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.save()
except:
return Response(
{"status": "Coordinates could not be saved."}, status=500
)
return Response({"status": "saved coords"})
class ExportTopoToXML(PermissionRequiredMixin, ViewSet):
+5 -1
View File
@@ -31,6 +31,7 @@ class DeviceFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
(
"q",
"filter_id",
"group",
"save_coords",
"show_unconnected",
"show_cables",
@@ -60,7 +61,10 @@ class DeviceFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
),
(None, ("tag",)),
)
group = forms.ModelChoiceField(
queryset=CoordinateGroup.objects.all(),
required=False
)
region_id = DynamicModelMultipleChoiceField(
queryset=Region.objects.all(), required=False, label=_("Region")
)
File diff suppressed because one or more lines are too long
+2 -1
View File
@@ -88,7 +88,8 @@ const coordSaveCheckbox = document.querySelector('#id_save_coords')
body: JSON.stringify({
node_id: nodeId,
x: nodePosition.x,
y: nodePosition.y
y: nodePosition.y,
group: topologyData.group
})
}
)
+9 -13
View File
@@ -8,7 +8,6 @@
"version": "2.0.17",
"resolved": "https://registry.npmjs.org/@egjs/hammerjs/-/hammerjs-2.0.17.tgz",
"integrity": "sha512-XQsZgjm2EcVUiZQf11UBJQfmZeEmOW8DpI1gsFeln6w0ae0ii4dMQEQ0kjl6DspdWX1aGY1/loyXnP0JS06e/A==",
"dev": true,
"requires": {
"@types/hammerjs": "^2.0.36"
}
@@ -23,8 +22,7 @@
"@types/hammerjs": {
"version": "2.0.41",
"resolved": "https://registry.npmjs.org/@types/hammerjs/-/hammerjs-2.0.41.tgz",
"integrity": "sha512-ewXv/ceBaJprikMcxCmWU1FKyMAQ2X7a9Gtmzw8fcg2kIePI1crERDM818W+XYrxqdBBOdlf2rm137bU+BltCA==",
"dev": true
"integrity": "sha512-ewXv/ceBaJprikMcxCmWU1FKyMAQ2X7a9Gtmzw8fcg2kIePI1crERDM818W+XYrxqdBBOdlf2rm137bU+BltCA=="
},
"anymatch": {
"version": "3.1.3",
@@ -70,8 +68,7 @@
"component-emitter": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz",
"integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==",
"dev": true
"integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg=="
},
"esbuild": {
"version": "0.12.29",
@@ -350,8 +347,7 @@
"keycharm": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/keycharm/-/keycharm-0.4.0.tgz",
"integrity": "sha512-TyQTtsabOVv3MeOpR92sIKk/br9wxS+zGj4BG7CR8YbK4jM3tyIBaF0zhzeBUMx36/Q/iQLOKKOT+3jOQtemRQ==",
"dev": true
"integrity": "sha512-TyQTtsabOVv3MeOpR92sIKk/br9wxS+zGj4BG7CR8YbK4jM3tyIBaF0zhzeBUMx36/Q/iQLOKKOT+3jOQtemRQ=="
},
"normalize-path": {
"version": "3.0.0",
@@ -417,8 +413,7 @@
"timsort": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/timsort/-/timsort-0.3.0.tgz",
"integrity": "sha512-qsdtZH+vMoCARQtyod4imc2nIJwg9Cc7lPRrw9CzF8ZKR0khdr8+2nX80PBhET3tcyTtJDxAffGh2rXH4tyU8A==",
"dev": true
"integrity": "sha512-qsdtZH+vMoCARQtyod4imc2nIJwg9Cc7lPRrw9CzF8ZKR0khdr8+2nX80PBhET3tcyTtJDxAffGh2rXH4tyU8A=="
},
"to-regex-range": {
"version": "5.0.1",
@@ -432,18 +427,19 @@
"uuid": {
"version": "8.3.2",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
"integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==",
"dev": true
"integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg=="
},
"vis-data": {
"version": "7.1.6",
"resolved": "https://registry.npmjs.org/vis-data/-/vis-data-7.1.6.tgz",
"integrity": "sha512-lG7LJdkawlKSXsdcEkxe/zRDyW29a4r7N7PMwxCPxK12/QIdqxJwcMxwjVj9ozdisRhP5TyWDHZwsgjmj0g6Dg=="
"integrity": "sha512-lG7LJdkawlKSXsdcEkxe/zRDyW29a4r7N7PMwxCPxK12/QIdqxJwcMxwjVj9ozdisRhP5TyWDHZwsgjmj0g6Dg==",
"requires": {}
},
"vis-network": {
"version": "9.1.6",
"resolved": "https://registry.npmjs.org/vis-network/-/vis-network-9.1.6.tgz",
"integrity": "sha512-Eiwx1JleAsUqfy4pzcsFngCVlCEdjAtRPB/OwCV7PHBm+o2jtE4IZPcPITAEGUlxvL4Fdw7/lZsfD32dL+IL6g=="
"integrity": "sha512-Eiwx1JleAsUqfy4pzcsFngCVlCEdjAtRPB/OwCV7PHBm+o2jtE4IZPcPITAEGUlxvL4Fdw7/lZsfD32dL+IL6g==",
"requires": {}
},
"vis-util": {
"version": "5.0.3",
+11 -11
View File
@@ -291,7 +291,7 @@
"resolved" "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz"
"version" "7.0.0"
"keycharm@^0.4.0":
"keycharm@^0.2.0 || ^0.3.0 || ^0.4.0", "keycharm@^0.4.0":
"integrity" "sha512-TyQTtsabOVv3MeOpR92sIKk/br9wxS+zGj4BG7CR8YbK4jM3tyIBaF0zhzeBUMx36/Q/iQLOKKOT+3jOQtemRQ=="
"resolved" "https://registry.npmjs.org/keycharm/-/keycharm-0.4.0.tgz"
"version" "0.4.0"
@@ -358,22 +358,22 @@
dependencies:
"is-number" "^7.0.0"
"uuid@^8.0.0":
"uuid@^3.4.0 || ^7.0.0 || ^8.0.0 || ^9.0.0", "uuid@^8.0.0":
"integrity" "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg=="
"resolved" "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz"
"version" "8.3.2"
"vis-data@^7.1.4":
"integrity" "sha512-usy+ePX1XnArNvJ5BavQod7YRuGQE1pjFl+pu7IS6rCom2EBoG0o1ZzCqf3l5US6MW51kYkLR+efxRbnjxNl7w=="
"resolved" "https://registry.npmjs.org/vis-data/-/vis-data-7.1.4.tgz"
"version" "7.1.4"
"vis-data@^6.3.0 || ^7.0.0", "vis-data@^7.1.6":
"integrity" "sha512-lG7LJdkawlKSXsdcEkxe/zRDyW29a4r7N7PMwxCPxK12/QIdqxJwcMxwjVj9ozdisRhP5TyWDHZwsgjmj0g6Dg=="
"resolved" "https://registry.npmjs.org/vis-data/-/vis-data-7.1.6.tgz"
"version" "7.1.6"
"vis-network@^9.1.2":
"integrity" "sha512-BdapguKg7sk3NvdZaDsM7T6rNhOBFz0/F4ZScxctK4klRzQPLQPTEcmbioXaZhMkkgWymzBR3lFCxL1q+eYyAw=="
"resolved" "https://registry.npmjs.org/vis-network/-/vis-network-9.1.2.tgz"
"version" "9.1.2"
"vis-network@^9.1.6":
"integrity" "sha512-Eiwx1JleAsUqfy4pzcsFngCVlCEdjAtRPB/OwCV7PHBm+o2jtE4IZPcPITAEGUlxvL4Fdw7/lZsfD32dL+IL6g=="
"resolved" "https://registry.npmjs.org/vis-network/-/vis-network-9.1.6.tgz"
"version" "9.1.6"
"vis-util@^5.0.3":
"vis-util@^5.0.1", "vis-util@^5.0.3":
"integrity" "sha512-Wf9STUcFrDzK4/Zr7B6epW2Kvm3ORNWF+WiwEz2dpf5RdWkLUXFSbLcuB88n1W6tCdFwVN+v3V4/Xmn9PeL39g=="
"resolved" "https://registry.npmjs.org/vis-util/-/vis-util-5.0.3.tgz"
"version" "5.0.3"
+32 -19
View File
@@ -25,7 +25,7 @@ from django.contrib.contenttypes.models import ContentType
from django.db.models import Q, QuerySet
from django.db.models.functions import Lower
from django.http import HttpRequest, HttpResponseRedirect, QueryDict
from django.shortcuts import render
from django.shortcuts import render, get_object_or_404
from django.views.generic import View
from extras.models import Tag
from wireless.models import WirelessLink
@@ -65,7 +65,7 @@ def get_image_for_entity(entity: Union[Device, Circuit, PowerPanel, PowerFeed]):
def create_node(
device: Union[Device, Circuit, PowerPanel, PowerFeed], save_coords: bool
device: Union[Device, Circuit, PowerPanel, PowerFeed], save_coords: bool, group_id="default"
):
node = {}
node_content = ""
@@ -156,22 +156,27 @@ def create_node(
node["href"] = device.get_absolute_url()
node["image"] = get_image_for_entity(device)
# Default group named "default" must always exist in order to make sure
# that coordinate values can be stored even if no coordinate group has been
# selected. The default group will be added automatically if it does not exist.
try:
group = CoordinateGroup.objects.get(name="default")
except CoordinateGroup.DoesNotExist:
if group_id is None or group_id == "default":
# Default group named "default" must always exist in order to make sure
# that coordinate values can be stored even if no coordinate group has been
# selected. The default group will be added automatically if it does not exist.
try:
group = CoordinateGroup(
name="default",
description="Automatically generated default group. If you delete "
"this group, all default coordinates are gone for good but "
"the group itself will be re-created."
)
group.save()
if CoordinateGroup.objects.filter(name="default"):
group = CoordinateGroup.objects.get(name="default")
group_id = group.pk
else:
group = CoordinateGroup(
name="default",
description="Automatically generated default group. If you delete "
"this group, all default coordinates are gone for good but "
"the group itself will be re-created."
)
group.save()
group_id = group.pk
except:
pass
group = get_object_or_404(CoordinateGroup, pk=group_id)
node["physics"] = True
if Coordinate.objects.filter(group=group, device=device.pk).values('x') and Coordinate.objects.filter(group=group, device=device.pk).values('y'):
@@ -296,6 +301,7 @@ def get_topology_data(
show_neighbors: bool,
show_power: bool,
show_wireless: bool,
group_id,
):
supported_termination_types = []
@@ -417,7 +423,7 @@ def get_topology_data(
] = circuit_termination.circuit
for d in nodes_circuits.values():
nodes.append(create_node(d, save_coords))
nodes.append(create_node(d, save_coords, group_id))
if show_power:
power_panels_ids = PowerPanel.objects.filter(
@@ -467,10 +473,10 @@ def get_topology_data(
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))
nodes.append(create_node(d, save_coords, group_id))
for d in nodes_powerpanel.values():
nodes.append(create_node(d, save_coords))
nodes.append(create_node(d, save_coords, group_id))
if show_logical_connections:
interfaces = Interface.objects.filter(
@@ -627,10 +633,11 @@ def get_topology_data(
results = {}
for d in nodes_devices.values():
nodes.append(create_node(d, save_coords))
nodes.append(create_node(d, save_coords, group_id))
results["nodes"] = nodes
results["edges"] = edges
results["group"] = group_id
return results
@@ -658,6 +665,11 @@ class TopologyHomeView(PermissionRequiredMixin, View):
save_coords, show_unconnected, show_power, show_circuit, show_logical_connections, show_single_cable_logical_conns, show_cables, show_wireless, show_neighbors = get_query_settings(request)
if "group" not in request.GET:
group_id = "default"
else:
group_id = request.GET["group"]
if not "draw_init" in request.GET or "draw_init" in request.GET and request.GET["draw_init"].lower() == "true":
topo_data = get_topology_data(
queryset=self.queryset,
@@ -671,6 +683,7 @@ class TopologyHomeView(PermissionRequiredMixin, View):
show_circuit=show_circuit,
show_power=show_power,
show_wireless=show_wireless,
group_id=group_id,
)
else: