netbox-cadplan 0.2.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.
Files changed (45) hide show
  1. netbox_cadplan-0.2.0/CHANGELOG.md +27 -0
  2. netbox_cadplan-0.2.0/CONTRIBUTING.md +14 -0
  3. netbox_cadplan-0.2.0/LICENSE +21 -0
  4. netbox_cadplan-0.2.0/MANIFEST.in +7 -0
  5. netbox_cadplan-0.2.0/PKG-INFO +124 -0
  6. netbox_cadplan-0.2.0/README.md +92 -0
  7. netbox_cadplan-0.2.0/netbox_cadplan/__init__.py +28 -0
  8. netbox_cadplan-0.2.0/netbox_cadplan/api/__init__.py +0 -0
  9. netbox_cadplan-0.2.0/netbox_cadplan/api/serializers.py +125 -0
  10. netbox_cadplan-0.2.0/netbox_cadplan/api/urls.py +12 -0
  11. netbox_cadplan-0.2.0/netbox_cadplan/api/views.py +30 -0
  12. netbox_cadplan-0.2.0/netbox_cadplan/choices.py +41 -0
  13. netbox_cadplan-0.2.0/netbox_cadplan/filtersets.py +20 -0
  14. netbox_cadplan-0.2.0/netbox_cadplan/forms.py +92 -0
  15. netbox_cadplan-0.2.0/netbox_cadplan/locale/fr/LC_MESSAGES/django.mo +0 -0
  16. netbox_cadplan-0.2.0/netbox_cadplan/locale/fr/LC_MESSAGES/django.po +408 -0
  17. netbox_cadplan-0.2.0/netbox_cadplan/locale/fr/LC_MESSAGES/djangojs.mo +0 -0
  18. netbox_cadplan-0.2.0/netbox_cadplan/locale/fr/LC_MESSAGES/djangojs.po +236 -0
  19. netbox_cadplan-0.2.0/netbox_cadplan/migrations/0001_initial.py +265 -0
  20. netbox_cadplan-0.2.0/netbox_cadplan/migrations/0002_placedobject_outside_wall.py +18 -0
  21. netbox_cadplan-0.2.0/netbox_cadplan/migrations/0003_planzone_source_polygon.py +18 -0
  22. netbox_cadplan-0.2.0/netbox_cadplan/migrations/__init__.py +0 -0
  23. netbox_cadplan-0.2.0/netbox_cadplan/models.py +261 -0
  24. netbox_cadplan-0.2.0/netbox_cadplan/navigation.py +29 -0
  25. netbox_cadplan-0.2.0/netbox_cadplan/signals.py +16 -0
  26. netbox_cadplan-0.2.0/netbox_cadplan/static/netbox_cadplan/css/plan.css +0 -0
  27. netbox_cadplan-0.2.0/netbox_cadplan/static/netbox_cadplan/js/plan_editor.js +2840 -0
  28. netbox_cadplan-0.2.0/netbox_cadplan/tables.py +24 -0
  29. netbox_cadplan-0.2.0/netbox_cadplan/template_content.py +19 -0
  30. netbox_cadplan-0.2.0/netbox_cadplan/templates/netbox_cadplan/devicetype_shape_edit.html +46 -0
  31. netbox_cadplan-0.2.0/netbox_cadplan/templates/netbox_cadplan/inc/devicetype_plan_shape_panel.html +34 -0
  32. netbox_cadplan-0.2.0/netbox_cadplan/templates/netbox_cadplan/location_tab.html +82 -0
  33. netbox_cadplan-0.2.0/netbox_cadplan/templates/netbox_cadplan/plan.html +180 -0
  34. netbox_cadplan-0.2.0/netbox_cadplan/templates/netbox_cadplan/plan_tab.html +109 -0
  35. netbox_cadplan-0.2.0/netbox_cadplan/urls.py +64 -0
  36. netbox_cadplan-0.2.0/netbox_cadplan/utils.py +630 -0
  37. netbox_cadplan-0.2.0/netbox_cadplan/views.py +1029 -0
  38. netbox_cadplan-0.2.0/netbox_cadplan.egg-info/PKG-INFO +124 -0
  39. netbox_cadplan-0.2.0/netbox_cadplan.egg-info/SOURCES.txt +44 -0
  40. netbox_cadplan-0.2.0/netbox_cadplan.egg-info/dependency_links.txt +1 -0
  41. netbox_cadplan-0.2.0/netbox_cadplan.egg-info/requires.txt +8 -0
  42. netbox_cadplan-0.2.0/netbox_cadplan.egg-info/top_level.txt +1 -0
  43. netbox_cadplan-0.2.0/pyproject.toml +54 -0
  44. netbox_cadplan-0.2.0/setup.cfg +15 -0
  45. netbox_cadplan-0.2.0/tests/test_utils.py +130 -0
@@ -0,0 +1,27 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ ## 0.2.0
6
+
7
+ - Add "Position by distance" tool and object selection highlighting on the
8
+ plan canvas.
9
+ - Add Center view control, fit-to-view canvas sizing, and pickable-list
10
+ refresh.
11
+ - Theme-aware label colors on canvas.
12
+ - Cache parsed DXF documents and only list layers with geometry, for faster
13
+ layer selection.
14
+ - Replace the layer selection list with a searchable dropdown with a loading
15
+ state.
16
+ - Link to device type shape configuration directly from unplaceable devices.
17
+ - Switch the default UI to English, with French available as a translation.
18
+ - Fix Properties panel not refreshing after right-click rotate on a selected
19
+ object.
20
+
21
+ ## 0.1.0
22
+
23
+ - Initial release: import DXF/DWG plans linked to Sites and Locations.
24
+ - Automatic zone splitting matched to descendant Locations, with per-location
25
+ SVG display tab.
26
+ - Device type plan shapes and interactive object placement on zones.
27
+ - DXF re-export.
@@ -0,0 +1,14 @@
1
+ # Contributing
2
+
3
+ Thank you for your interest in contributing to netbox-cadplan.
4
+
5
+ - Please open issues for bugs or feature requests on the repository issue tracker.
6
+ - For code contributions, fork the repository, create a feature branch, and open a pull request.
7
+ - Ensure linters and tests pass before submitting a PR.
8
+
9
+ Guidelines:
10
+ - Keep changes focused and well-documented.
11
+ - Add tests for new features or bug fixes.
12
+ - Run `ruff` and `black` to format and lint code.
13
+
14
+ Maintainer contact: see repository settings.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Simon Lacroix
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,7 @@
1
+ include LICENSE
2
+ include README.md
3
+ include CHANGELOG.md
4
+ include CONTRIBUTING.md
5
+ recursive-include netbox_cadplan/templates *
6
+ recursive-include netbox_cadplan/static *
7
+ recursive-include netbox_cadplan/locale *
@@ -0,0 +1,124 @@
1
+ Metadata-Version: 2.4
2
+ Name: netbox-cadplan
3
+ Version: 0.2.0
4
+ Summary: NetBox plugin: visual floor-plan management linked to Sites and Locations, with DXF/DWG import
5
+ Author: G1tHub-PRO, SelfSimon
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/SelfSimon/netbox-cadplan
8
+ Project-URL: Repository, https://github.com/SelfSimon/netbox-cadplan
9
+ Project-URL: Bug Tracker, https://github.com/SelfSimon/netbox-cadplan/issues
10
+ Keywords: netbox,plugin,plan,floorplan,dxf,dwg,sites,locations
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Environment :: Plugins
13
+ Classifier: Framework :: Django
14
+ Classifier: Intended Audience :: System Administrators
15
+ Classifier: License :: OSI Approved :: MIT License
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.10
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Topic :: System :: Networking
21
+ Requires-Python: >=3.10
22
+ Description-Content-Type: text/markdown
23
+ License-File: LICENSE
24
+ Requires-Dist: ezdxf
25
+ Requires-Dist: svgwrite
26
+ Provides-Extra: dev
27
+ Requires-Dist: pytest>=7.0; extra == "dev"
28
+ Requires-Dist: ruff>=0.12.0; extra == "dev"
29
+ Requires-Dist: black>=23.0; extra == "dev"
30
+ Requires-Dist: pre-commit>=2.20; extra == "dev"
31
+ Dynamic: license-file
32
+
33
+ # netbox-cadplan
34
+
35
+ NetBox plugin for visual floor-plan management. A plan is attached to a
36
+ NetBox `Site` (required) and, optionally, to a `Location` within that site,
37
+ based on an imported DXF/DWG file. The plan is split into zones associated
38
+ with locations (descendant Locations of the plan's Location, or the whole
39
+ site if no Location is specified), with the SVG of each location displayed
40
+ in a dedicated tab on its NetBox page.
41
+
42
+ ## Compatibility
43
+
44
+ | Package | NetBox | Python |
45
+ |---------|---------------|--------|
46
+ | 0.1.x | 4.6.0 – 4.6.99 | ≥ 3.10 |
47
+
48
+ ## Installation
49
+
50
+ ### 1. Install the package
51
+
52
+ ```bash
53
+ pip install netbox-cadplan
54
+ # or from source:
55
+ pip install git+https://github.com/simonlacroix/netbox-cadplan.git
56
+ ```
57
+
58
+ ### 2. Enable the plugin in NetBox
59
+
60
+ Add the plugin to `configuration.py` (or `configuration/plugins.py` depending
61
+ on your setup):
62
+
63
+ ```python
64
+ PLUGINS = [
65
+ # ... other plugins
66
+ 'netbox_cadplan',
67
+ ]
68
+ ```
69
+
70
+ ### 3. Run migrations and restart NetBox
71
+
72
+ ```bash
73
+ python manage.py migrate netbox_cadplan
74
+ sudo systemctl restart netbox netbox-rq
75
+ ```
76
+
77
+ ## Usage
78
+
79
+ - Import a DXF/DWG file to create a **Plan**, linked to a Site and optionally
80
+ a Location.
81
+ - The plan is automatically split into **zones** matched to Locations
82
+ descending from the plan's scope.
83
+ - Each Location gets a dedicated tab showing the SVG of its zone.
84
+ - Objects (device types with a configured plan shape) can be placed on a
85
+ zone and repositioned interactively.
86
+ - Plans can be re-exported to DXF.
87
+
88
+ ## License
89
+
90
+ MIT — see [LICENSE](LICENSE).
91
+
92
+ ## Contributors
93
+
94
+ - [Simon Lacroix](https://github.com/simonlacroix) — Original author
95
+
96
+ ## Developer setup
97
+
98
+ Quick setup for contributors — creates a virtualenv, installs development
99
+ dependencies and installs the `pre-commit` git hooks.
100
+
101
+ PowerShell (Windows):
102
+
103
+ ```powershell
104
+ python -m venv .venv
105
+ .\.venv\Scripts\Activate.ps1
106
+ python -m pip install --upgrade pip
107
+ pip install -e ".[dev]"
108
+ pre-commit install
109
+ pre-commit run --all-files
110
+ ```
111
+
112
+ POSIX (macOS / Linux):
113
+
114
+ ```bash
115
+ python -m venv .venv
116
+ source .venv/bin/activate
117
+ python -m pip install --upgrade pip
118
+ pip install -e '.[dev]'
119
+ pre-commit install
120
+ pre-commit run --all-files
121
+ ```
122
+
123
+ If you prefer automation, run `scripts/bootstrap.ps1` on Windows or
124
+ `scripts/bootstrap.sh` on POSIX systems to perform these steps.
@@ -0,0 +1,92 @@
1
+ # netbox-cadplan
2
+
3
+ NetBox plugin for visual floor-plan management. A plan is attached to a
4
+ NetBox `Site` (required) and, optionally, to a `Location` within that site,
5
+ based on an imported DXF/DWG file. The plan is split into zones associated
6
+ with locations (descendant Locations of the plan's Location, or the whole
7
+ site if no Location is specified), with the SVG of each location displayed
8
+ in a dedicated tab on its NetBox page.
9
+
10
+ ## Compatibility
11
+
12
+ | Package | NetBox | Python |
13
+ |---------|---------------|--------|
14
+ | 0.1.x | 4.6.0 – 4.6.99 | ≥ 3.10 |
15
+
16
+ ## Installation
17
+
18
+ ### 1. Install the package
19
+
20
+ ```bash
21
+ pip install netbox-cadplan
22
+ # or from source:
23
+ pip install git+https://github.com/simonlacroix/netbox-cadplan.git
24
+ ```
25
+
26
+ ### 2. Enable the plugin in NetBox
27
+
28
+ Add the plugin to `configuration.py` (or `configuration/plugins.py` depending
29
+ on your setup):
30
+
31
+ ```python
32
+ PLUGINS = [
33
+ # ... other plugins
34
+ 'netbox_cadplan',
35
+ ]
36
+ ```
37
+
38
+ ### 3. Run migrations and restart NetBox
39
+
40
+ ```bash
41
+ python manage.py migrate netbox_cadplan
42
+ sudo systemctl restart netbox netbox-rq
43
+ ```
44
+
45
+ ## Usage
46
+
47
+ - Import a DXF/DWG file to create a **Plan**, linked to a Site and optionally
48
+ a Location.
49
+ - The plan is automatically split into **zones** matched to Locations
50
+ descending from the plan's scope.
51
+ - Each Location gets a dedicated tab showing the SVG of its zone.
52
+ - Objects (device types with a configured plan shape) can be placed on a
53
+ zone and repositioned interactively.
54
+ - Plans can be re-exported to DXF.
55
+
56
+ ## License
57
+
58
+ MIT — see [LICENSE](LICENSE).
59
+
60
+ ## Contributors
61
+
62
+ - [Simon Lacroix](https://github.com/simonlacroix) — Original author
63
+
64
+ ## Developer setup
65
+
66
+ Quick setup for contributors — creates a virtualenv, installs development
67
+ dependencies and installs the `pre-commit` git hooks.
68
+
69
+ PowerShell (Windows):
70
+
71
+ ```powershell
72
+ python -m venv .venv
73
+ .\.venv\Scripts\Activate.ps1
74
+ python -m pip install --upgrade pip
75
+ pip install -e ".[dev]"
76
+ pre-commit install
77
+ pre-commit run --all-files
78
+ ```
79
+
80
+ POSIX (macOS / Linux):
81
+
82
+ ```bash
83
+ python -m venv .venv
84
+ source .venv/bin/activate
85
+ python -m pip install --upgrade pip
86
+ pip install -e '.[dev]'
87
+ pre-commit install
88
+ pre-commit run --all-files
89
+ ```
90
+
91
+ If you prefer automation, run `scripts/bootstrap.ps1` on Windows or
92
+ `scripts/bootstrap.sh` on POSIX systems to perform these steps.
@@ -0,0 +1,28 @@
1
+ try:
2
+ from netbox.plugins import PluginConfig
3
+ except Exception:
4
+ # Minimal fallback for local development / tests when NetBox is not
5
+ # available. This avoids import-time errors during pytest collection.
6
+ class PluginConfig: # type: ignore
7
+ pass
8
+
9
+
10
+ class NetBoxCadPlanConfig(PluginConfig):
11
+ name = "netbox_cadplan"
12
+ verbose_name = "NetBox CadPlan"
13
+ description = "Visual floor-plan management linked to Sites and Locations"
14
+ version = "0.2.0"
15
+ author = "Simon Lacroix"
16
+ author_email = "simonlacroix@live.ca"
17
+ base_url = "cadplan"
18
+ min_version = "4.6.0"
19
+ max_version = "4.6.99"
20
+ required_settings = []
21
+ default_settings = {}
22
+
23
+ def ready(self):
24
+ super().ready()
25
+ from . import signals # noqa: F401
26
+
27
+
28
+ config = NetBoxCadPlanConfig
File without changes
@@ -0,0 +1,125 @@
1
+ from core.models import ObjectType
2
+ from dcim.api.serializers import (
3
+ DeviceTypeSerializer,
4
+ LocationSerializer,
5
+ SiteSerializer,
6
+ )
7
+ from netbox.api.fields import ContentTypeField
8
+ from netbox.api.gfk_fields import GFKSerializerField
9
+ from netbox.api.serializers import NetBoxModelSerializer
10
+ from rest_framework import serializers
11
+
12
+ from ..models import DeviceTypeShape, PlacedObject, Plan, PlanZone
13
+
14
+
15
+ class PlanSerializer(NetBoxModelSerializer):
16
+ url = serializers.HyperlinkedIdentityField(
17
+ view_name="plugins-api:netbox_cadplan-api:plan-detail"
18
+ )
19
+ site = SiteSerializer(nested=True)
20
+ location = LocationSerializer(nested=True, required=False, allow_null=True)
21
+
22
+ class Meta:
23
+ model = Plan
24
+ fields = [
25
+ "id",
26
+ "url",
27
+ "display",
28
+ "name",
29
+ "site",
30
+ "location",
31
+ "dxf_file",
32
+ "selected_layer",
33
+ "width_px",
34
+ "height_px",
35
+ "mm_per_px",
36
+ "tags",
37
+ "custom_fields",
38
+ "created",
39
+ "last_updated",
40
+ ]
41
+ brief_fields = ("id", "url", "display", "name")
42
+
43
+
44
+ class PlanZoneSerializer(NetBoxModelSerializer):
45
+ url = serializers.HyperlinkedIdentityField(
46
+ view_name="plugins-api:netbox_cadplan-api:planzone-detail"
47
+ )
48
+ plan = PlanSerializer(nested=True)
49
+ location = LocationSerializer(nested=True, required=False, allow_null=True)
50
+
51
+ class Meta:
52
+ model = PlanZone
53
+ fields = [
54
+ "id",
55
+ "url",
56
+ "display",
57
+ "plan",
58
+ "number",
59
+ "location",
60
+ "polygon_data",
61
+ "svg_file",
62
+ "label",
63
+ "tags",
64
+ "custom_fields",
65
+ "created",
66
+ "last_updated",
67
+ ]
68
+ brief_fields = ("id", "url", "display", "number", "label")
69
+
70
+
71
+ class DeviceTypeShapeSerializer(NetBoxModelSerializer):
72
+ url = serializers.HyperlinkedIdentityField(
73
+ view_name="plugins-api:netbox_cadplan-api:devicetypeshape-detail"
74
+ )
75
+ device_type = DeviceTypeSerializer(nested=True)
76
+
77
+ class Meta:
78
+ model = DeviceTypeShape
79
+ fields = [
80
+ "id",
81
+ "url",
82
+ "display",
83
+ "device_type",
84
+ "shape",
85
+ "width",
86
+ "depth",
87
+ "diameter",
88
+ "unit",
89
+ "tags",
90
+ "custom_fields",
91
+ "created",
92
+ "last_updated",
93
+ ]
94
+ brief_fields = ("id", "url", "display", "device_type", "shape")
95
+
96
+
97
+ class PlacedObjectSerializer(NetBoxModelSerializer):
98
+ url = serializers.HyperlinkedIdentityField(
99
+ view_name="plugins-api:netbox_cadplan-api:placedobject-detail"
100
+ )
101
+ zone = PlanZoneSerializer(nested=True)
102
+ object_type = ContentTypeField(queryset=ObjectType.objects.all())
103
+ object = GFKSerializerField(source="content_object", read_only=True)
104
+
105
+ class Meta:
106
+ model = PlacedObject
107
+ fields = [
108
+ "id",
109
+ "url",
110
+ "display",
111
+ "zone",
112
+ "object_type",
113
+ "object_id",
114
+ "object",
115
+ "x",
116
+ "y",
117
+ "rotation",
118
+ "snap_to_wall",
119
+ "name_position",
120
+ "tags",
121
+ "custom_fields",
122
+ "created",
123
+ "last_updated",
124
+ ]
125
+ brief_fields = ("id", "url", "display", "object_type", "object_id")
@@ -0,0 +1,12 @@
1
+ from netbox.api.routers import NetBoxRouter
2
+
3
+ from . import views
4
+
5
+ app_name = "netbox_cadplan"
6
+ router = NetBoxRouter()
7
+ router.register("plans", views.PlanViewSet)
8
+ router.register("plan-zones", views.PlanZoneViewSet)
9
+ router.register("device-type-shapes", views.DeviceTypeShapeViewSet)
10
+ router.register("placed-objects", views.PlacedObjectViewSet)
11
+
12
+ urlpatterns = router.urls
@@ -0,0 +1,30 @@
1
+ from netbox.api.viewsets import NetBoxModelViewSet
2
+
3
+ from .. import filtersets, models
4
+ from .serializers import (
5
+ DeviceTypeShapeSerializer,
6
+ PlacedObjectSerializer,
7
+ PlanSerializer,
8
+ PlanZoneSerializer,
9
+ )
10
+
11
+
12
+ class PlanViewSet(NetBoxModelViewSet):
13
+ queryset = models.Plan.objects.all()
14
+ serializer_class = PlanSerializer
15
+ filterset_class = filtersets.PlanFilterSet
16
+
17
+
18
+ class PlanZoneViewSet(NetBoxModelViewSet):
19
+ queryset = models.PlanZone.objects.all()
20
+ serializer_class = PlanZoneSerializer
21
+
22
+
23
+ class DeviceTypeShapeViewSet(NetBoxModelViewSet):
24
+ queryset = models.DeviceTypeShape.objects.all()
25
+ serializer_class = DeviceTypeShapeSerializer
26
+
27
+
28
+ class PlacedObjectViewSet(NetBoxModelViewSet):
29
+ queryset = models.PlacedObject.objects.all()
30
+ serializer_class = PlacedObjectSerializer
@@ -0,0 +1,41 @@
1
+ from django.utils.translation import gettext_lazy as _
2
+ from utilities.choices import ChoiceSet
3
+
4
+
5
+ class ShapeChoices(ChoiceSet):
6
+
7
+ RECTANGLE = "rectangle"
8
+ CIRCLE = "circle"
9
+
10
+ CHOICES = (
11
+ (RECTANGLE, _("Rectangle")),
12
+ (CIRCLE, _("Circle")),
13
+ )
14
+
15
+
16
+ class LengthUnitChoices(ChoiceSet):
17
+
18
+ UNIT_CENTIMETER = "cm"
19
+ UNIT_INCH = "in"
20
+
21
+ CHOICES = (
22
+ (UNIT_CENTIMETER, _("Centimeters")),
23
+ (UNIT_INCH, _("Inches")),
24
+ )
25
+
26
+
27
+ class NamePositionChoices(ChoiceSet):
28
+
29
+ TOP = "top"
30
+ BOTTOM = "bottom"
31
+ LEFT = "left"
32
+ RIGHT = "right"
33
+ CENTER = "center"
34
+
35
+ CHOICES = (
36
+ (TOP, _("Top")),
37
+ (BOTTOM, _("Bottom")),
38
+ (LEFT, _("Left")),
39
+ (RIGHT, _("Right")),
40
+ (CENTER, _("Center")),
41
+ )
@@ -0,0 +1,20 @@
1
+ import django_filters
2
+ from django.db.models import Q
3
+ from netbox.filtersets import NetBoxModelFilterSet
4
+
5
+ from .models import Plan
6
+
7
+
8
+ class PlanFilterSet(NetBoxModelFilterSet):
9
+ q = django_filters.CharFilter(method="search", label="Search")
10
+
11
+ class Meta:
12
+ model = Plan
13
+ fields = ("id", "name", "site", "location", "selected_layer")
14
+
15
+ def search(self, queryset, name, value):
16
+ if not value.strip():
17
+ return queryset
18
+ return queryset.filter(
19
+ Q(name__icontains=value) | Q(selected_layer__icontains=value)
20
+ )
@@ -0,0 +1,92 @@
1
+ from dcim.models import Location, Site
2
+ from django.core.exceptions import ValidationError
3
+ from django.utils.translation import gettext_lazy as _
4
+ from netbox.forms import NetBoxModelForm
5
+ from utilities.forms.fields import DynamicModelChoiceField
6
+ from utilities.forms.rendering import FieldSet, InlineFields
7
+
8
+ from .models import DeviceTypeShape, Plan
9
+
10
+ MAX_DXF_UPLOAD_SIZE = 50 * 1024 * 1024 # 50 Mo
11
+
12
+
13
+ def _validate_dxf_size(dxf_file):
14
+ if dxf_file and dxf_file.size > MAX_DXF_UPLOAD_SIZE:
15
+ raise ValidationError(
16
+ _("The DXF/DWG file exceeds the maximum allowed size (50 MB).")
17
+ )
18
+ return dxf_file
19
+
20
+
21
+ class PlanForm(NetBoxModelForm):
22
+ site = DynamicModelChoiceField(
23
+ queryset=Site.objects.all(),
24
+ required=True,
25
+ )
26
+ location = DynamicModelChoiceField(
27
+ queryset=Location.objects.all(),
28
+ required=False,
29
+ query_params={"site_id": "$site"},
30
+ help_text=_(
31
+ "Optional NetBox Location (leave empty for a plan covering the whole site)"
32
+ ),
33
+ )
34
+
35
+ fieldsets = (FieldSet("name", "site", "location", "dxf_file", name=_("Plan")),)
36
+
37
+ class Meta:
38
+ model = Plan
39
+ fields = ("name", "site", "location", "dxf_file")
40
+
41
+ def clean_dxf_file(self):
42
+ return _validate_dxf_size(self.cleaned_data.get("dxf_file"))
43
+
44
+ def clean(self):
45
+ super().clean()
46
+ cleaned_data = self.cleaned_data
47
+ site = cleaned_data.get("site")
48
+ location = cleaned_data.get("location")
49
+ if site and location and location.site_id != site.id:
50
+ self.add_error(
51
+ "location",
52
+ _("The selected location does not belong to the chosen site."),
53
+ )
54
+ return cleaned_data
55
+
56
+
57
+ class ReimportDxfForm(NetBoxModelForm):
58
+ """
59
+ Replaces only the DXF file of an already-confirmed plan (not name/site/location,
60
+ unlike PlanForm) — clears selected_layer on save to make the layer selection
61
+ panel reappear (see PlanReimportDxfView).
62
+ """
63
+
64
+ fieldsets = (FieldSet("dxf_file", name=_("Reimport a DXF")),)
65
+
66
+ class Meta:
67
+ model = Plan
68
+ fields = ("dxf_file",)
69
+
70
+ def clean_dxf_file(self):
71
+ return _validate_dxf_size(self.cleaned_data.get("dxf_file"))
72
+
73
+ def save(self, commit=True):
74
+ instance = super().save(commit=False)
75
+ instance.selected_layer = ""
76
+ if commit:
77
+ instance.save()
78
+ return instance
79
+
80
+
81
+ class DeviceTypeShapeForm(NetBoxModelForm):
82
+ fieldsets = (
83
+ FieldSet(
84
+ "shape",
85
+ InlineFields("width", "depth", "diameter", "unit", label=_("Dimensions")),
86
+ name=_("Shape"),
87
+ ),
88
+ )
89
+
90
+ class Meta:
91
+ model = DeviceTypeShape
92
+ fields = ("shape", "width", "depth", "diameter", "unit")