netbox-plugin-plan-drawing 1.0.0__tar.gz → 1.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {netbox_plugin_plan_drawing-1.0.0/netbox_plugin_plan_drawing.egg-info → netbox_plugin_plan_drawing-1.1.0}/PKG-INFO +1 -1
- {netbox_plugin_plan_drawing-1.0.0 → netbox_plugin_plan_drawing-1.1.0}/netbox_plugin_plan_drawing/__init__.py +1 -1
- {netbox_plugin_plan_drawing-1.0.0 → netbox_plugin_plan_drawing-1.1.0}/netbox_plugin_plan_drawing/static/netbox_plugin_plan_drawing/plan-drawing.css +31 -0
- {netbox_plugin_plan_drawing-1.0.0 → netbox_plugin_plan_drawing-1.1.0}/netbox_plugin_plan_drawing/static/netbox_plugin_plan_drawing/viewer.js +61 -15
- netbox_plugin_plan_drawing-1.1.0/netbox_plugin_plan_drawing/templates/netbox_plugin_plan_drawing/inc/element_contents.html +21 -0
- {netbox_plugin_plan_drawing-1.0.0 → netbox_plugin_plan_drawing-1.1.0}/netbox_plugin_plan_drawing/templates/netbox_plugin_plan_drawing/plandrawing.html +9 -0
- {netbox_plugin_plan_drawing-1.0.0 → netbox_plugin_plan_drawing-1.1.0}/netbox_plugin_plan_drawing/tests/test_views.py +24 -0
- {netbox_plugin_plan_drawing-1.0.0 → netbox_plugin_plan_drawing-1.1.0}/netbox_plugin_plan_drawing/views.py +77 -1
- {netbox_plugin_plan_drawing-1.0.0 → netbox_plugin_plan_drawing-1.1.0/netbox_plugin_plan_drawing.egg-info}/PKG-INFO +1 -1
- {netbox_plugin_plan_drawing-1.0.0 → netbox_plugin_plan_drawing-1.1.0}/netbox_plugin_plan_drawing.egg-info/SOURCES.txt +1 -0
- {netbox_plugin_plan_drawing-1.0.0 → netbox_plugin_plan_drawing-1.1.0}/pyproject.toml +1 -1
- {netbox_plugin_plan_drawing-1.0.0 → netbox_plugin_plan_drawing-1.1.0}/LICENSE +0 -0
- {netbox_plugin_plan_drawing-1.0.0 → netbox_plugin_plan_drawing-1.1.0}/MANIFEST.in +0 -0
- {netbox_plugin_plan_drawing-1.0.0 → netbox_plugin_plan_drawing-1.1.0}/README.md +0 -0
- {netbox_plugin_plan_drawing-1.0.0 → netbox_plugin_plan_drawing-1.1.0}/drawio-overrides/LICENSE +0 -0
- {netbox_plugin_plan_drawing-1.0.0 → netbox_plugin_plan_drawing-1.1.0}/drawio-overrides/js/PostConfig.js +0 -0
- {netbox_plugin_plan_drawing-1.0.0 → netbox_plugin_plan_drawing-1.1.0}/drawio-overrides/js/PreConfig.js +0 -0
- {netbox_plugin_plan_drawing-1.0.0 → netbox_plugin_plan_drawing-1.1.0}/netbox_plugin_plan_drawing/api/__init__.py +0 -0
- {netbox_plugin_plan_drawing-1.0.0 → netbox_plugin_plan_drawing-1.1.0}/netbox_plugin_plan_drawing/api/serializers.py +0 -0
- {netbox_plugin_plan_drawing-1.0.0 → netbox_plugin_plan_drawing-1.1.0}/netbox_plugin_plan_drawing/api/urls.py +0 -0
- {netbox_plugin_plan_drawing-1.0.0 → netbox_plugin_plan_drawing-1.1.0}/netbox_plugin_plan_drawing/api/views.py +0 -0
- {netbox_plugin_plan_drawing-1.0.0 → netbox_plugin_plan_drawing-1.1.0}/netbox_plugin_plan_drawing/assets.py +0 -0
- {netbox_plugin_plan_drawing-1.0.0 → netbox_plugin_plan_drawing-1.1.0}/netbox_plugin_plan_drawing/drawio.py +0 -0
- {netbox_plugin_plan_drawing-1.0.0 → netbox_plugin_plan_drawing-1.1.0}/netbox_plugin_plan_drawing/filtersets.py +0 -0
- {netbox_plugin_plan_drawing-1.0.0 → netbox_plugin_plan_drawing-1.1.0}/netbox_plugin_plan_drawing/forms.py +0 -0
- {netbox_plugin_plan_drawing-1.0.0 → netbox_plugin_plan_drawing-1.1.0}/netbox_plugin_plan_drawing/management/__init__.py +0 -0
- {netbox_plugin_plan_drawing-1.0.0 → netbox_plugin_plan_drawing-1.1.0}/netbox_plugin_plan_drawing/management/commands/__init__.py +0 -0
- {netbox_plugin_plan_drawing-1.0.0 → netbox_plugin_plan_drawing-1.1.0}/netbox_plugin_plan_drawing/management/commands/plan_drawing_changelog_stats.py +0 -0
- {netbox_plugin_plan_drawing-1.0.0 → netbox_plugin_plan_drawing-1.1.0}/netbox_plugin_plan_drawing/migrations/0001_initial.py +0 -0
- {netbox_plugin_plan_drawing-1.0.0 → netbox_plugin_plan_drawing-1.1.0}/netbox_plugin_plan_drawing/migrations/__init__.py +0 -0
- {netbox_plugin_plan_drawing-1.0.0 → netbox_plugin_plan_drawing-1.1.0}/netbox_plugin_plan_drawing/models.py +0 -0
- {netbox_plugin_plan_drawing-1.0.0 → netbox_plugin_plan_drawing-1.1.0}/netbox_plugin_plan_drawing/navigation.py +0 -0
- {netbox_plugin_plan_drawing-1.0.0 → netbox_plugin_plan_drawing-1.1.0}/netbox_plugin_plan_drawing/object_types.py +0 -0
- {netbox_plugin_plan_drawing-1.0.0 → netbox_plugin_plan_drawing-1.1.0}/netbox_plugin_plan_drawing/search.py +0 -0
- {netbox_plugin_plan_drawing-1.0.0 → netbox_plugin_plan_drawing-1.1.0}/netbox_plugin_plan_drawing/static/netbox_plugin_plan_drawing/drawio-netbox.js +0 -0
- {netbox_plugin_plan_drawing-1.0.0 → netbox_plugin_plan_drawing-1.1.0}/netbox_plugin_plan_drawing/static/netbox_plugin_plan_drawing/editor.js +0 -0
- {netbox_plugin_plan_drawing-1.0.0 → netbox_plugin_plan_drawing-1.1.0}/netbox_plugin_plan_drawing/tables.py +0 -0
- {netbox_plugin_plan_drawing-1.0.0 → netbox_plugin_plan_drawing-1.1.0}/netbox_plugin_plan_drawing/template_content.py +0 -0
- {netbox_plugin_plan_drawing-1.0.0 → netbox_plugin_plan_drawing-1.1.0}/netbox_plugin_plan_drawing/templates/netbox_plugin_plan_drawing/inc/object_drawings_panel.html +0 -0
- {netbox_plugin_plan_drawing-1.0.0 → netbox_plugin_plan_drawing-1.1.0}/netbox_plugin_plan_drawing/templates/netbox_plugin_plan_drawing/plandrawing_draw.html +0 -0
- {netbox_plugin_plan_drawing-1.0.0 → netbox_plugin_plan_drawing-1.1.0}/netbox_plugin_plan_drawing/templatetags/__init__.py +0 -0
- {netbox_plugin_plan_drawing-1.0.0 → netbox_plugin_plan_drawing-1.1.0}/netbox_plugin_plan_drawing/templatetags/plan_drawing.py +0 -0
- {netbox_plugin_plan_drawing-1.0.0 → netbox_plugin_plan_drawing-1.1.0}/netbox_plugin_plan_drawing/tests/__init__.py +0 -0
- {netbox_plugin_plan_drawing-1.0.0 → netbox_plugin_plan_drawing-1.1.0}/netbox_plugin_plan_drawing/tests/test_api.py +0 -0
- {netbox_plugin_plan_drawing-1.0.0 → netbox_plugin_plan_drawing-1.1.0}/netbox_plugin_plan_drawing/tests/test_drawio.py +0 -0
- {netbox_plugin_plan_drawing-1.0.0 → netbox_plugin_plan_drawing-1.1.0}/netbox_plugin_plan_drawing/tests/test_models.py +0 -0
- {netbox_plugin_plan_drawing-1.0.0 → netbox_plugin_plan_drawing-1.1.0}/netbox_plugin_plan_drawing/tests/test_object_types.py +0 -0
- {netbox_plugin_plan_drawing-1.0.0 → netbox_plugin_plan_drawing-1.1.0}/netbox_plugin_plan_drawing/tests/utils.py +0 -0
- {netbox_plugin_plan_drawing-1.0.0 → netbox_plugin_plan_drawing-1.1.0}/netbox_plugin_plan_drawing/urls.py +0 -0
- {netbox_plugin_plan_drawing-1.0.0 → netbox_plugin_plan_drawing-1.1.0}/netbox_plugin_plan_drawing.egg-info/dependency_links.txt +0 -0
- {netbox_plugin_plan_drawing-1.0.0 → netbox_plugin_plan_drawing-1.1.0}/netbox_plugin_plan_drawing.egg-info/requires.txt +0 -0
- {netbox_plugin_plan_drawing-1.0.0 → netbox_plugin_plan_drawing-1.1.0}/netbox_plugin_plan_drawing.egg-info/top_level.txt +0 -0
- {netbox_plugin_plan_drawing-1.0.0 → netbox_plugin_plan_drawing-1.1.0}/scripts/fetch_drawio.py +0 -0
- {netbox_plugin_plan_drawing-1.0.0 → netbox_plugin_plan_drawing-1.1.0}/setup.cfg +0 -0
- {netbox_plugin_plan_drawing-1.0.0 → netbox_plugin_plan_drawing-1.1.0}/setup.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: netbox-plugin-plan-drawing
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.1.0
|
|
4
4
|
Summary: NetBox plugin for plan drawings (floor plans etc.) with draw.io, linking drawing elements to NetBox objects
|
|
5
5
|
Author-email: Stefan Bethke <stb@lassitu.de>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -5,7 +5,7 @@ class PlanDrawingConfig(PluginConfig):
|
|
|
5
5
|
name = 'netbox_plugin_plan_drawing'
|
|
6
6
|
verbose_name = 'Plan Drawings'
|
|
7
7
|
description = 'Plan drawings (floor plans etc.) edited with draw.io, with elements linked to NetBox objects'
|
|
8
|
-
version = '1.
|
|
8
|
+
version = '1.1.0'
|
|
9
9
|
author = 'Stefan Bethke'
|
|
10
10
|
author_email = 'stb@lassitu.de'
|
|
11
11
|
base_url = 'plan-drawing'
|
|
@@ -36,6 +36,37 @@
|
|
|
36
36
|
background-color: color-mix(in srgb, var(--plan-drawing-select-color) 12%, transparent);
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
+
/* The objects in a location or rack are only listed while it is selected, or while one of the
|
|
40
|
+
objects in it is selected from the list. */
|
|
41
|
+
.plan-drawing-element:not(.active, .plan-drawing-expanded) .plan-drawing-element-contents {
|
|
42
|
+
display: none;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
.plan-drawing-contents {
|
|
46
|
+
padding-left: 0.75rem;
|
|
47
|
+
border-left: 1px solid var(--bs-border-color);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
.plan-drawing-node {
|
|
51
|
+
padding: 0 0.25rem;
|
|
52
|
+
cursor: default;
|
|
53
|
+
border-radius: var(--bs-border-radius-sm);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/* Objects in a location or rack that are linked in the drawing as well: clicking them selects
|
|
57
|
+
their elements. */
|
|
58
|
+
.plan-drawing-node-linked {
|
|
59
|
+
cursor: pointer;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
.plan-drawing-node-linked:hover {
|
|
63
|
+
background-color: var(--bs-tertiary-bg);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
.plan-drawing-node.active {
|
|
67
|
+
background-color: color-mix(in srgb, var(--plan-drawing-select-color) 20%, transparent);
|
|
68
|
+
}
|
|
69
|
+
|
|
39
70
|
/* Listing only the current page's elements: their page names are redundant. */
|
|
40
71
|
.plan-drawing-this-page .plan-drawing-element-page {
|
|
41
72
|
display: none;
|
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
* the list of elements linked to NetBox objects: clicking either one highlights the other,
|
|
4
4
|
* searching the list highlights all matching elements, and ?highlight=<type>:<id> in the page
|
|
5
5
|
* URL highlights the elements of that object when the page opens. The list shows the elements
|
|
6
|
-
* of the current page, or of all pages.
|
|
6
|
+
* of the current page, or of all pages. A selected location or rack lists the objects in it;
|
|
7
|
+
* clicking those that are linked in the drawing as well selects their elements.
|
|
7
8
|
*/
|
|
8
9
|
(function () {
|
|
9
10
|
'use strict';
|
|
@@ -52,8 +53,9 @@
|
|
|
52
53
|
this.container = container;
|
|
53
54
|
this.elements = elements;
|
|
54
55
|
this.rows = Array.from(document.querySelectorAll('.plan-drawing-element'));
|
|
56
|
+
this.nodes = Array.from(document.querySelectorAll('.plan-drawing-node-linked'));
|
|
55
57
|
this.highlights = [];
|
|
56
|
-
this.
|
|
58
|
+
this.hoverHighlights = [];
|
|
57
59
|
this.selected = [];
|
|
58
60
|
this.matches = [];
|
|
59
61
|
this.searchTerms = [];
|
|
@@ -245,13 +247,13 @@
|
|
|
245
247
|
this.rows.forEach(function (row) {
|
|
246
248
|
const index = parseInt(row.dataset.index, 10);
|
|
247
249
|
row.addEventListener('click', function (evt) {
|
|
248
|
-
if (evt.target.closest('a')) {
|
|
250
|
+
if (evt.target.closest('a, .plan-drawing-element-contents')) {
|
|
249
251
|
return;
|
|
250
252
|
}
|
|
251
253
|
self.select([index], { switchPage: true, scrollDrawing: true });
|
|
252
254
|
});
|
|
253
255
|
row.addEventListener('keydown', function (evt) {
|
|
254
|
-
if (evt.key === 'Enter' || evt.key === ' ') {
|
|
256
|
+
if (evt.target === row && (evt.key === 'Enter' || evt.key === ' ')) {
|
|
255
257
|
evt.preventDefault();
|
|
256
258
|
self.select([index], { switchPage: true, scrollDrawing: true });
|
|
257
259
|
}
|
|
@@ -264,6 +266,40 @@
|
|
|
264
266
|
});
|
|
265
267
|
});
|
|
266
268
|
|
|
269
|
+
// The objects in a location or rack that are linked in the drawing as well select their
|
|
270
|
+
// elements, and keep the location or rack expanded.
|
|
271
|
+
this.nodes.forEach(function (node) {
|
|
272
|
+
const type = node.dataset.objectType;
|
|
273
|
+
const id = parseInt(node.dataset.objectId, 10);
|
|
274
|
+
const row = parseInt(node.closest('.plan-drawing-element').dataset.index, 10);
|
|
275
|
+
node.indices = [];
|
|
276
|
+
self.elements.forEach(function (element, index) {
|
|
277
|
+
if (element.object_type === type && element.object_id === id) {
|
|
278
|
+
node.indices.push(index);
|
|
279
|
+
}
|
|
280
|
+
});
|
|
281
|
+
const select = function () {
|
|
282
|
+
self.select(node.indices, { switchPage: true, scrollDrawing: true, expand: row });
|
|
283
|
+
};
|
|
284
|
+
node.addEventListener('click', function (evt) {
|
|
285
|
+
if (!evt.target.closest('a')) {
|
|
286
|
+
select();
|
|
287
|
+
}
|
|
288
|
+
});
|
|
289
|
+
node.addEventListener('keydown', function (evt) {
|
|
290
|
+
if (evt.target === node && (evt.key === 'Enter' || evt.key === ' ')) {
|
|
291
|
+
evt.preventDefault();
|
|
292
|
+
select();
|
|
293
|
+
}
|
|
294
|
+
});
|
|
295
|
+
node.addEventListener('mouseenter', function () {
|
|
296
|
+
self.hover(node.indices);
|
|
297
|
+
});
|
|
298
|
+
node.addEventListener('mouseleave', function () {
|
|
299
|
+
self.hover(row);
|
|
300
|
+
});
|
|
301
|
+
});
|
|
302
|
+
|
|
267
303
|
const search = document.getElementById('plan-drawing-search');
|
|
268
304
|
if (search) {
|
|
269
305
|
search.addEventListener('input', function () {
|
|
@@ -332,7 +368,14 @@
|
|
|
332
368
|
this.refreshHighlights();
|
|
333
369
|
|
|
334
370
|
this.rows.forEach(function (row) {
|
|
335
|
-
|
|
371
|
+
const index = parseInt(row.dataset.index, 10);
|
|
372
|
+
row.classList.toggle('active', indices.indexOf(index) >= 0);
|
|
373
|
+
row.classList.toggle('plan-drawing-expanded', index === options.expand);
|
|
374
|
+
});
|
|
375
|
+
this.nodes.forEach(function (node) {
|
|
376
|
+
node.classList.toggle('active', node.indices.some(function (index) {
|
|
377
|
+
return indices.indexOf(index) >= 0;
|
|
378
|
+
}));
|
|
336
379
|
});
|
|
337
380
|
|
|
338
381
|
if (options.scrollList && indices.length > 0) {
|
|
@@ -350,17 +393,20 @@
|
|
|
350
393
|
}
|
|
351
394
|
};
|
|
352
395
|
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
396
|
+
/** Highlights the elements with the given index or indices on the current page. */
|
|
397
|
+
PlanDrawingViewer.prototype.hover = function (indices) {
|
|
398
|
+
const self = this;
|
|
399
|
+
this.hoverHighlights.forEach(function (highlight) {
|
|
400
|
+
highlight.destroy();
|
|
401
|
+
});
|
|
402
|
+
this.hoverHighlights = [];
|
|
403
|
+
[].concat(indices == null ? [] : indices).forEach(function (index) {
|
|
404
|
+
const cell = self.cellForElement(index);
|
|
405
|
+
const highlight = cell && self.addHighlight(cell, HOVER_COLOR, 3);
|
|
406
|
+
if (highlight) {
|
|
407
|
+
self.hoverHighlights.push(highlight);
|
|
362
408
|
}
|
|
363
|
-
}
|
|
409
|
+
});
|
|
364
410
|
};
|
|
365
411
|
|
|
366
412
|
PlanDrawingViewer.prototype.matchesSearch = function (element) {
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{% load i18n %}
|
|
2
|
+
<ul class="list-unstyled mb-0 plan-drawing-contents">
|
|
3
|
+
{% for node in nodes %}
|
|
4
|
+
<li>
|
|
5
|
+
{% if node.linked %}
|
|
6
|
+
<div class="d-flex justify-content-between gap-2 plan-drawing-node plan-drawing-node-linked" data-object-type="{{ node.object_type }}" data-object-id="{{ node.object_id }}" tabindex="0" title="{% trans 'Show in the drawing' %}">
|
|
7
|
+
{% else %}
|
|
8
|
+
<div class="d-flex justify-content-between gap-2 plan-drawing-node">
|
|
9
|
+
{% endif %}
|
|
10
|
+
<span>
|
|
11
|
+
<a href="{{ node.url }}">{{ node.display }}</a>
|
|
12
|
+
{% if node.linked %}<i class="mdi mdi-link-variant text-muted"></i>{% endif %}
|
|
13
|
+
</span>
|
|
14
|
+
<span class="text-muted text-nowrap">{{ node.object_type_label }}{% if node.position is not None %} · U{{ node.position|floatformat:"-1" }}{% endif %}</span>
|
|
15
|
+
</div>
|
|
16
|
+
{% if node.children %}
|
|
17
|
+
{% include 'netbox_plugin_plan_drawing/inc/element_contents.html' with nodes=node.children %}
|
|
18
|
+
{% endif %}
|
|
19
|
+
</li>
|
|
20
|
+
{% endfor %}
|
|
21
|
+
</ul>
|
|
@@ -77,6 +77,15 @@
|
|
|
77
77
|
<small class="text-muted">
|
|
78
78
|
{{ element.object_type_label }}{% if element.label and element.label != element.display %} · {{ element.label }}{% endif %}
|
|
79
79
|
</small>
|
|
80
|
+
{% if element.contents is not None %}
|
|
81
|
+
<div class="small mt-1 plan-drawing-element-contents">
|
|
82
|
+
{% if element.contents %}
|
|
83
|
+
{% include 'netbox_plugin_plan_drawing/inc/element_contents.html' with nodes=element.contents %}
|
|
84
|
+
{% else %}
|
|
85
|
+
<span class="text-muted">{% trans "No objects in it." %}</span>
|
|
86
|
+
{% endif %}
|
|
87
|
+
</div>
|
|
88
|
+
{% endif %}
|
|
80
89
|
</div>
|
|
81
90
|
{% empty %}
|
|
82
91
|
<div class="list-group-item text-muted">{% trans "No drawing elements are linked to NetBox objects." %}</div>
|
|
@@ -43,6 +43,30 @@ class PlanDrawingViewsTestCase(TestCase):
|
|
|
43
43
|
self.assertEqual(response.context['elements'][0]['display'], str(self.rack))
|
|
44
44
|
self.assertEqual(response.context['elements'][0]['url'], self.rack.get_absolute_url())
|
|
45
45
|
|
|
46
|
+
def test_detail_lists_location_and_rack_contents(self):
|
|
47
|
+
self.drawing.content = self.content(
|
|
48
|
+
linked_cell('b', 'Room', 'dcim.location', self.location.pk),
|
|
49
|
+
linked_cell('c', 'Device', 'dcim.device', self.device.pk),
|
|
50
|
+
)
|
|
51
|
+
self.drawing.save()
|
|
52
|
+
|
|
53
|
+
response = self.client.get(self.drawing.get_absolute_url())
|
|
54
|
+
|
|
55
|
+
contents = {e['object_type']: e['contents'] for e in response.context['elements']}
|
|
56
|
+
self.assertIsNone(contents['dcim.device'])
|
|
57
|
+
[rack] = contents['dcim.location']
|
|
58
|
+
self.assertEqual((rack['display'], rack['url'], rack['linked']), (str(self.rack), self.rack.get_absolute_url(), False))
|
|
59
|
+
[device] = rack['children']
|
|
60
|
+
self.assertEqual((device['object_type'], device['object_id'], device['linked']), ('dcim.device', self.device.pk, True))
|
|
61
|
+
self.assertContains(response, f'data-object-type="dcim.device" data-object-id="{self.device.pk}"')
|
|
62
|
+
|
|
63
|
+
def test_detail_lists_rack_contents(self):
|
|
64
|
+
response = self.client.get(self.drawing.get_absolute_url())
|
|
65
|
+
|
|
66
|
+
[device] = response.context['elements'][0]['contents']
|
|
67
|
+
self.assertEqual((device['display'], device['linked']), (str(self.device), False))
|
|
68
|
+
self.assertContains(response, f'href="{self.device.get_absolute_url()}"')
|
|
69
|
+
|
|
46
70
|
def test_detail_lists_elements_in_page_order(self):
|
|
47
71
|
self.drawing.content = drawing_file(
|
|
48
72
|
('z', 'Zeta', graph_model(linked_cell('a', 'Rack', 'dcim.rack', self.rack.pk))),
|
|
@@ -3,13 +3,16 @@ from collections import defaultdict
|
|
|
3
3
|
|
|
4
4
|
from django.core.exceptions import ValidationError
|
|
5
5
|
from django.db import transaction
|
|
6
|
+
from django.db.models import F, Q
|
|
6
7
|
from django.http import JsonResponse
|
|
7
8
|
from django.middleware.csrf import get_token
|
|
8
9
|
from django.templatetags.static import static
|
|
10
|
+
from django.utils.text import capfirst
|
|
9
11
|
from django.shortcuts import get_object_or_404
|
|
10
12
|
from django.urls import NoReverseMatch, reverse
|
|
11
13
|
from django.views import View
|
|
12
14
|
|
|
15
|
+
from dcim.models import Device, Location, PowerPanel, Rack
|
|
13
16
|
from netbox.views import generic
|
|
14
17
|
from utilities.permissions import get_permission_for_model
|
|
15
18
|
from utilities.query import count_related
|
|
@@ -26,11 +29,82 @@ from .tables import PlanDrawingTable
|
|
|
26
29
|
DRAWIO_INDEX = 'netbox_plugin_plan_drawing/drawio/index.html'
|
|
27
30
|
|
|
28
31
|
|
|
32
|
+
def location_and_rack_contents(objects, user):
|
|
33
|
+
"""
|
|
34
|
+
Return the trees of objects in the given locations and racks, keyed by (model, pk) of the
|
|
35
|
+
location or rack. A location contains its child locations, racks, devices not mounted in a
|
|
36
|
+
rack, and power panels; a rack contains its devices, top to bottom; and a device contains the
|
|
37
|
+
devices installed in its device bays. Objects in the given list are marked as linked. Objects
|
|
38
|
+
the user may not view are left out.
|
|
39
|
+
"""
|
|
40
|
+
linked = {(type(obj), obj.pk) for obj in objects}
|
|
41
|
+
location_ids = set()
|
|
42
|
+
for obj in objects:
|
|
43
|
+
if isinstance(obj, Location):
|
|
44
|
+
location_ids.update(obj.get_descendants(include_self=True).values_list('pk', flat=True))
|
|
45
|
+
linked_rack_ids = [obj.pk for obj in objects if isinstance(obj, Rack)]
|
|
46
|
+
if not location_ids and not linked_rack_ids:
|
|
47
|
+
return {}
|
|
48
|
+
|
|
49
|
+
locations = Location.objects.restrict(user, 'view').filter(pk__in=location_ids)
|
|
50
|
+
racks = list(Rack.objects.restrict(user, 'view').filter(Q(location__in=location_ids) | Q(pk__in=linked_rack_ids)))
|
|
51
|
+
devices = Device.objects.restrict(user, 'view').filter(
|
|
52
|
+
Q(rack__in=[rack.pk for rack in racks]) | Q(location__in=location_ids, rack__isnull=True),
|
|
53
|
+
).select_related('parent_bay').order_by(F('position').desc(nulls_last=True), 'name')
|
|
54
|
+
panels = PowerPanel.objects.restrict(user, 'view').filter(location__in=location_ids)
|
|
55
|
+
|
|
56
|
+
nodes = {}
|
|
57
|
+
|
|
58
|
+
def add(obj, *parents, position=None):
|
|
59
|
+
"""
|
|
60
|
+
Add the object below the first of the parents, given as (model, pk), that is in the tree.
|
|
61
|
+
"""
|
|
62
|
+
node = nodes[(type(obj), obj.pk)] = {
|
|
63
|
+
'display': str(obj),
|
|
64
|
+
'url': obj.get_absolute_url(),
|
|
65
|
+
'object_type': f'{obj._meta.app_label}.{obj._meta.model_name}',
|
|
66
|
+
'object_id': obj.pk,
|
|
67
|
+
'object_type_label': capfirst(obj._meta.verbose_name),
|
|
68
|
+
'position': position,
|
|
69
|
+
'linked': (type(obj), obj.pk) in linked,
|
|
70
|
+
'children': [],
|
|
71
|
+
}
|
|
72
|
+
for parent in parents:
|
|
73
|
+
if parent in nodes:
|
|
74
|
+
nodes[parent]['children'].append(node)
|
|
75
|
+
break
|
|
76
|
+
|
|
77
|
+
# Locations are ordered by their path, so that parents are added before their children.
|
|
78
|
+
for location in locations:
|
|
79
|
+
add(location, (Location, location.parent_id))
|
|
80
|
+
for rack in racks:
|
|
81
|
+
add(rack, (Location, rack.location_id))
|
|
82
|
+
# Devices in device bays go below their parent device, once it has been added; devices whose
|
|
83
|
+
# parent is not in the tree go below their rack or location.
|
|
84
|
+
pending = list(devices)
|
|
85
|
+
while pending:
|
|
86
|
+
remaining = []
|
|
87
|
+
pending_ids = {device.pk for device in pending}
|
|
88
|
+
for device in pending:
|
|
89
|
+
parent_bay = getattr(device, 'parent_bay', None)
|
|
90
|
+
parent = (Device, parent_bay.device_id) if parent_bay else None
|
|
91
|
+
if parent and parent not in nodes and parent_bay.device_id in pending_ids:
|
|
92
|
+
remaining.append(device)
|
|
93
|
+
continue
|
|
94
|
+
add(device, parent, (Rack, device.rack_id), (Location, device.location_id), position=device.position)
|
|
95
|
+
if len(remaining) == len(pending):
|
|
96
|
+
break
|
|
97
|
+
pending = remaining
|
|
98
|
+
for panel in panels:
|
|
99
|
+
add(panel, (Location, panel.location_id))
|
|
100
|
+
return nodes
|
|
101
|
+
|
|
102
|
+
|
|
29
103
|
def linked_elements(drawing, user):
|
|
30
104
|
"""
|
|
31
105
|
Return the drawing's linked elements, with the NetBox objects they are linked to, for the
|
|
32
106
|
drawing viewer, in the order of the drawing's pages. Objects the user may not view are shown
|
|
33
|
-
without their name.
|
|
107
|
+
without their name. Locations and racks come with the objects in them.
|
|
34
108
|
"""
|
|
35
109
|
elements = list(drawing.elements.select_related('assigned_object_type'))
|
|
36
110
|
|
|
@@ -44,6 +118,7 @@ def linked_elements(drawing, user):
|
|
|
44
118
|
continue
|
|
45
119
|
for obj in model.objects.restrict(user, 'view').filter(pk__in=ids):
|
|
46
120
|
objects[(content_type.pk, obj.pk)] = obj
|
|
121
|
+
contents = location_and_rack_contents(list(objects.values()), user)
|
|
47
122
|
|
|
48
123
|
result = []
|
|
49
124
|
for element in elements:
|
|
@@ -60,6 +135,7 @@ def linked_elements(drawing, user):
|
|
|
60
135
|
'object_id': element.assigned_object_id,
|
|
61
136
|
'display': str(obj) if obj else None,
|
|
62
137
|
'url': obj.get_absolute_url() if obj else None,
|
|
138
|
+
'contents': contents[(model, obj.pk)]['children'] if isinstance(obj, (Location, Rack)) else None,
|
|
63
139
|
})
|
|
64
140
|
|
|
65
141
|
try:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: netbox-plugin-plan-drawing
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.1.0
|
|
4
4
|
Summary: NetBox plugin for plan drawings (floor plans etc.) with draw.io, linking drawing elements to NetBox objects
|
|
5
5
|
Author-email: Stefan Bethke <stb@lassitu.de>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -39,6 +39,7 @@ netbox_plugin_plan_drawing/static/netbox_plugin_plan_drawing/plan-drawing.css
|
|
|
39
39
|
netbox_plugin_plan_drawing/static/netbox_plugin_plan_drawing/viewer.js
|
|
40
40
|
netbox_plugin_plan_drawing/templates/netbox_plugin_plan_drawing/plandrawing.html
|
|
41
41
|
netbox_plugin_plan_drawing/templates/netbox_plugin_plan_drawing/plandrawing_draw.html
|
|
42
|
+
netbox_plugin_plan_drawing/templates/netbox_plugin_plan_drawing/inc/element_contents.html
|
|
42
43
|
netbox_plugin_plan_drawing/templates/netbox_plugin_plan_drawing/inc/object_drawings_panel.html
|
|
43
44
|
netbox_plugin_plan_drawing/templatetags/__init__.py
|
|
44
45
|
netbox_plugin_plan_drawing/templatetags/plan_drawing.py
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{netbox_plugin_plan_drawing-1.0.0 → netbox_plugin_plan_drawing-1.1.0}/drawio-overrides/LICENSE
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{netbox_plugin_plan_drawing-1.0.0 → netbox_plugin_plan_drawing-1.1.0}/scripts/fetch_drawio.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|