netbox-cable-patcher 0.4.1__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_cable_patcher-0.4.1/LICENSE +1 -0
- netbox_cable_patcher-0.4.1/PKG-INFO +85 -0
- netbox_cable_patcher-0.4.1/README.md +57 -0
- netbox_cable_patcher-0.4.1/netbox_cable_patcher/__init__.py +20 -0
- netbox_cable_patcher-0.4.1/netbox_cable_patcher/api/__init__.py +1 -0
- netbox_cable_patcher-0.4.1/netbox_cable_patcher/api/serializers.py +239 -0
- netbox_cable_patcher-0.4.1/netbox_cable_patcher/api/urls.py +12 -0
- netbox_cable_patcher-0.4.1/netbox_cable_patcher/api/views.py +500 -0
- netbox_cable_patcher-0.4.1/netbox_cable_patcher/filtersets.py +1 -0
- netbox_cable_patcher-0.4.1/netbox_cable_patcher/forms.py +1 -0
- netbox_cable_patcher-0.4.1/netbox_cable_patcher/models.py +2 -0
- netbox_cable_patcher-0.4.1/netbox_cable_patcher/navigation.py +9 -0
- netbox_cable_patcher-0.4.1/netbox_cable_patcher/static/netbox_cable_patcher/css/patcher.css +802 -0
- netbox_cable_patcher-0.4.1/netbox_cable_patcher/static/netbox_cable_patcher/js/patcher.js +2093 -0
- netbox_cable_patcher-0.4.1/netbox_cable_patcher/tables.py +1 -0
- netbox_cable_patcher-0.4.1/netbox_cable_patcher/templates/netbox_cable_patcher/patcher.html +402 -0
- netbox_cable_patcher-0.4.1/netbox_cable_patcher/urls.py +7 -0
- netbox_cable_patcher-0.4.1/netbox_cable_patcher/views.py +22 -0
- netbox_cable_patcher-0.4.1/netbox_cable_patcher.egg-info/PKG-INFO +85 -0
- netbox_cable_patcher-0.4.1/netbox_cable_patcher.egg-info/SOURCES.txt +24 -0
- netbox_cable_patcher-0.4.1/netbox_cable_patcher.egg-info/dependency_links.txt +1 -0
- netbox_cable_patcher-0.4.1/netbox_cable_patcher.egg-info/requires.txt +8 -0
- netbox_cable_patcher-0.4.1/netbox_cable_patcher.egg-info/top_level.txt +1 -0
- netbox_cable_patcher-0.4.1/pyproject.toml +53 -0
- netbox_cable_patcher-0.4.1/setup.cfg +4 -0
- netbox_cable_patcher-0.4.1/tests/test_netbox_cable_patcher.py +504 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: netbox-cable-patcher
|
|
3
|
+
Version: 0.4.1
|
|
4
|
+
Summary: NetBox plugin providing a visual patch bay interface for managing cable connections.
|
|
5
|
+
Author: NetBox Cable Patcher Contributors
|
|
6
|
+
License-Expression: Apache-2.0
|
|
7
|
+
Project-URL: Documentation, https://github.com/netbox-community/netbox-cable-patcher/blob/main/README.md
|
|
8
|
+
Project-URL: Source, https://github.com/netbox-community/netbox-cable-patcher
|
|
9
|
+
Project-URL: Tracker, https://github.com/netbox-community/netbox-cable-patcher/issues
|
|
10
|
+
Classifier: Development Status :: 3 - Alpha
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: Natural Language :: English
|
|
13
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Requires-Python: >=3.10.0
|
|
18
|
+
Description-Content-Type: text/markdown
|
|
19
|
+
License-File: LICENSE
|
|
20
|
+
Provides-Extra: test
|
|
21
|
+
Requires-Dist: black==24.3.0; extra == "test"
|
|
22
|
+
Requires-Dist: check-manifest==0.49; extra == "test"
|
|
23
|
+
Requires-Dist: flake8; extra == "test"
|
|
24
|
+
Requires-Dist: flake8-pyproject; extra == "test"
|
|
25
|
+
Requires-Dist: pre-commit==3.7.0; extra == "test"
|
|
26
|
+
Requires-Dist: pytest==8.1.1; extra == "test"
|
|
27
|
+
Dynamic: license-file
|
|
28
|
+
|
|
29
|
+
# NetBox Cable Patcher
|
|
30
|
+
|
|
31
|
+
NetBox plugin for netbox_cable_patcher.
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
* Free software: Apache-2.0
|
|
35
|
+
* Documentation: https://.github.io/netbox-cable-patcher/
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
## Features
|
|
39
|
+
|
|
40
|
+
The features the plugin provides should be listed here.
|
|
41
|
+
|
|
42
|
+
## Compatibility
|
|
43
|
+
|
|
44
|
+
| NetBox Version | Plugin Version |
|
|
45
|
+
|----------------|----------------|
|
|
46
|
+
| 4.0 | 0.1.0 |
|
|
47
|
+
|
|
48
|
+
## Installing
|
|
49
|
+
|
|
50
|
+
For adding to a NetBox Docker setup see
|
|
51
|
+
[the general instructions for using netbox-docker with plugins](https://github.com/netbox-community/netbox-docker/wiki/Using-Netbox-Plugins).
|
|
52
|
+
|
|
53
|
+
While this is still in development and not yet on pypi you can install with pip:
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
pip install git+https://github.com//netbox-cable-patcher
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
or by adding to your `local_requirements.txt` or `plugin_requirements.txt` (netbox-docker):
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
git+https://github.com//netbox-cable-patcher
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Enable the plugin in `/opt/netbox/netbox/netbox/configuration.py`,
|
|
66
|
+
or if you use netbox-docker, your `/configuration/plugins.py` file :
|
|
67
|
+
|
|
68
|
+
```python
|
|
69
|
+
PLUGINS = [
|
|
70
|
+
'netbox-cable-patcher'
|
|
71
|
+
]
|
|
72
|
+
|
|
73
|
+
PLUGINS_CONFIG = {
|
|
74
|
+
"netbox-cable-patcher": {},
|
|
75
|
+
}
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## Credits
|
|
79
|
+
|
|
80
|
+
Based on the NetBox plugin tutorial:
|
|
81
|
+
|
|
82
|
+
- [demo repository](https://github.com/netbox-community/netbox-plugin-demo)
|
|
83
|
+
- [tutorial](https://github.com/netbox-community/netbox-plugin-tutorial)
|
|
84
|
+
|
|
85
|
+
This package was created with [Cookiecutter](https://github.com/audreyr/cookiecutter) and the [`netbox-community/cookiecutter-netbox-plugin`](https://github.com/netbox-community/cookiecutter-netbox-plugin) project template.
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# NetBox Cable Patcher
|
|
2
|
+
|
|
3
|
+
NetBox plugin for netbox_cable_patcher.
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
* Free software: Apache-2.0
|
|
7
|
+
* Documentation: https://.github.io/netbox-cable-patcher/
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
## Features
|
|
11
|
+
|
|
12
|
+
The features the plugin provides should be listed here.
|
|
13
|
+
|
|
14
|
+
## Compatibility
|
|
15
|
+
|
|
16
|
+
| NetBox Version | Plugin Version |
|
|
17
|
+
|----------------|----------------|
|
|
18
|
+
| 4.0 | 0.1.0 |
|
|
19
|
+
|
|
20
|
+
## Installing
|
|
21
|
+
|
|
22
|
+
For adding to a NetBox Docker setup see
|
|
23
|
+
[the general instructions for using netbox-docker with plugins](https://github.com/netbox-community/netbox-docker/wiki/Using-Netbox-Plugins).
|
|
24
|
+
|
|
25
|
+
While this is still in development and not yet on pypi you can install with pip:
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
pip install git+https://github.com//netbox-cable-patcher
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
or by adding to your `local_requirements.txt` or `plugin_requirements.txt` (netbox-docker):
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
git+https://github.com//netbox-cable-patcher
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Enable the plugin in `/opt/netbox/netbox/netbox/configuration.py`,
|
|
38
|
+
or if you use netbox-docker, your `/configuration/plugins.py` file :
|
|
39
|
+
|
|
40
|
+
```python
|
|
41
|
+
PLUGINS = [
|
|
42
|
+
'netbox-cable-patcher'
|
|
43
|
+
]
|
|
44
|
+
|
|
45
|
+
PLUGINS_CONFIG = {
|
|
46
|
+
"netbox-cable-patcher": {},
|
|
47
|
+
}
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Credits
|
|
51
|
+
|
|
52
|
+
Based on the NetBox plugin tutorial:
|
|
53
|
+
|
|
54
|
+
- [demo repository](https://github.com/netbox-community/netbox-plugin-demo)
|
|
55
|
+
- [tutorial](https://github.com/netbox-community/netbox-plugin-tutorial)
|
|
56
|
+
|
|
57
|
+
This package was created with [Cookiecutter](https://github.com/audreyr/cookiecutter) and the [`netbox-community/cookiecutter-netbox-plugin`](https://github.com/netbox-community/cookiecutter-netbox-plugin) project template.
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"""Top-level package for NetBox Cable Patcher."""
|
|
2
|
+
|
|
3
|
+
__author__ = """John Doe"""
|
|
4
|
+
__email__ = ""
|
|
5
|
+
__version__ = "0.4.1"
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
from netbox.plugins import PluginConfig
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class NetBoxCablePatcherConfig(PluginConfig):
|
|
12
|
+
name = "netbox_cable_patcher"
|
|
13
|
+
verbose_name = "Cable Patcher"
|
|
14
|
+
description = "Visual patch bay interface for NetBox with interactive cable connections."
|
|
15
|
+
version = __version__
|
|
16
|
+
base_url = "cable-patcher"
|
|
17
|
+
min_version = "4.0.0"
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
config = NetBoxCablePatcherConfig
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# API module for NetBox Cable Patcher
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
from rest_framework import serializers
|
|
2
|
+
from dcim.models import (
|
|
3
|
+
Site, Location, Rack, Device, Interface,
|
|
4
|
+
FrontPort, RearPort, PowerPort, PowerOutlet,
|
|
5
|
+
ConsolePort, ConsoleServerPort, Cable,
|
|
6
|
+
ModuleBay, Module, ModuleType
|
|
7
|
+
)
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class SiteSerializer(serializers.ModelSerializer):
|
|
11
|
+
class Meta:
|
|
12
|
+
model = Site
|
|
13
|
+
fields = ['id', 'name', 'slug']
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class LocationSerializer(serializers.ModelSerializer):
|
|
17
|
+
class Meta:
|
|
18
|
+
model = Location
|
|
19
|
+
fields = ['id', 'name', 'slug', 'site']
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class RackSerializer(serializers.ModelSerializer):
|
|
23
|
+
class Meta:
|
|
24
|
+
model = Rack
|
|
25
|
+
fields = ['id', 'name', 'site', 'location']
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class PortSerializer(serializers.Serializer):
|
|
29
|
+
"""Generic serializer for any port type."""
|
|
30
|
+
id = serializers.IntegerField()
|
|
31
|
+
name = serializers.CharField()
|
|
32
|
+
type = serializers.CharField(source='get_type_display', required=False, allow_null=True)
|
|
33
|
+
cable_id = serializers.SerializerMethodField()
|
|
34
|
+
connected_endpoint = serializers.SerializerMethodField()
|
|
35
|
+
port_type = serializers.SerializerMethodField()
|
|
36
|
+
module_bay_id = serializers.SerializerMethodField()
|
|
37
|
+
|
|
38
|
+
def get_cable_id(self, obj):
|
|
39
|
+
if hasattr(obj, 'cable') and obj.cable:
|
|
40
|
+
return obj.cable.id
|
|
41
|
+
return None
|
|
42
|
+
|
|
43
|
+
def get_connected_endpoint(self, obj):
|
|
44
|
+
if hasattr(obj, 'connected_endpoints') and obj.connected_endpoints:
|
|
45
|
+
endpoints = obj.connected_endpoints
|
|
46
|
+
if endpoints:
|
|
47
|
+
endpoint = endpoints[0]
|
|
48
|
+
return {
|
|
49
|
+
'id': endpoint.id,
|
|
50
|
+
'name': endpoint.name if hasattr(endpoint, 'name') else str(endpoint),
|
|
51
|
+
'device_id': endpoint.device.id if hasattr(endpoint, 'device') else None,
|
|
52
|
+
'device_name': endpoint.device.name if hasattr(endpoint, 'device') else None,
|
|
53
|
+
'type': endpoint.__class__.__name__
|
|
54
|
+
}
|
|
55
|
+
return None
|
|
56
|
+
|
|
57
|
+
def get_port_type(self, obj):
|
|
58
|
+
return obj.__class__.__name__
|
|
59
|
+
|
|
60
|
+
untagged_vlan_vid = serializers.SerializerMethodField()
|
|
61
|
+
|
|
62
|
+
def get_untagged_vlan_vid(self, obj):
|
|
63
|
+
if hasattr(obj, 'untagged_vlan') and obj.untagged_vlan:
|
|
64
|
+
return obj.untagged_vlan.vid
|
|
65
|
+
return None
|
|
66
|
+
|
|
67
|
+
def get_module_bay_id(self, obj):
|
|
68
|
+
if hasattr(obj, 'module') and obj.module:
|
|
69
|
+
return obj.module.module_bay_id
|
|
70
|
+
return None
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
class ModuleBaySerializer(serializers.Serializer):
|
|
74
|
+
"""Serializer for module bay data within a device."""
|
|
75
|
+
id = serializers.IntegerField()
|
|
76
|
+
name = serializers.CharField()
|
|
77
|
+
position = serializers.CharField(allow_null=True, required=False)
|
|
78
|
+
label = serializers.CharField(allow_blank=True, required=False)
|
|
79
|
+
installed_module = serializers.SerializerMethodField()
|
|
80
|
+
|
|
81
|
+
def get_installed_module(self, obj):
|
|
82
|
+
if not hasattr(obj, 'installed_module') or not obj.installed_module:
|
|
83
|
+
return None
|
|
84
|
+
mod = obj.installed_module
|
|
85
|
+
return {
|
|
86
|
+
'id': mod.id,
|
|
87
|
+
'module_type': {
|
|
88
|
+
'id': mod.module_type_id,
|
|
89
|
+
'model': mod.module_type.model,
|
|
90
|
+
'manufacturer': mod.module_type.manufacturer.name,
|
|
91
|
+
},
|
|
92
|
+
'status': mod.status,
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
class ModuleTypeSearchSerializer(serializers.ModelSerializer):
|
|
97
|
+
"""Serializer for module type search results."""
|
|
98
|
+
manufacturer_name = serializers.CharField(source='manufacturer.name', read_only=True)
|
|
99
|
+
|
|
100
|
+
class Meta:
|
|
101
|
+
model = ModuleType
|
|
102
|
+
fields = ['id', 'model', 'manufacturer_name', 'description', 'part_number']
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
class ModuleInstallSerializer(serializers.Serializer):
|
|
106
|
+
"""Serializer for validating module installation requests."""
|
|
107
|
+
module_type_id = serializers.IntegerField()
|
|
108
|
+
serial = serializers.CharField(required=False, allow_blank=True, default='')
|
|
109
|
+
asset_tag = serializers.CharField(required=False, allow_blank=True, allow_null=True, default=None)
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
class DeviceWithPortsSerializer(serializers.ModelSerializer):
|
|
113
|
+
interfaces = serializers.SerializerMethodField()
|
|
114
|
+
power_ports = serializers.SerializerMethodField()
|
|
115
|
+
power_outlets = serializers.SerializerMethodField()
|
|
116
|
+
front_ports = serializers.SerializerMethodField()
|
|
117
|
+
rear_ports = serializers.SerializerMethodField()
|
|
118
|
+
console_ports = serializers.SerializerMethodField()
|
|
119
|
+
console_server_ports = serializers.SerializerMethodField()
|
|
120
|
+
module_bays = serializers.SerializerMethodField()
|
|
121
|
+
|
|
122
|
+
class Meta:
|
|
123
|
+
model = Device
|
|
124
|
+
fields = [
|
|
125
|
+
'id', 'name', 'device_type', 'rack', 'position',
|
|
126
|
+
'interfaces', 'power_ports', 'power_outlets',
|
|
127
|
+
'front_ports', 'rear_ports', 'console_ports', 'console_server_ports',
|
|
128
|
+
'module_bays'
|
|
129
|
+
]
|
|
130
|
+
|
|
131
|
+
def get_interfaces(self, obj):
|
|
132
|
+
# Exclude non-physical interface types (virtual, lag, bridge)
|
|
133
|
+
qs = obj.interfaces.exclude(type__in=['virtual', 'lag', 'bridge'])
|
|
134
|
+
return PortSerializer(qs, many=True).data
|
|
135
|
+
|
|
136
|
+
def get_power_ports(self, obj):
|
|
137
|
+
return PortSerializer(obj.powerports.all(), many=True).data
|
|
138
|
+
|
|
139
|
+
def get_power_outlets(self, obj):
|
|
140
|
+
return PortSerializer(obj.poweroutlets.all(), many=True).data
|
|
141
|
+
|
|
142
|
+
def get_front_ports(self, obj):
|
|
143
|
+
return PortSerializer(obj.frontports.all(), many=True).data
|
|
144
|
+
|
|
145
|
+
def get_rear_ports(self, obj):
|
|
146
|
+
return PortSerializer(obj.rearports.all(), many=True).data
|
|
147
|
+
|
|
148
|
+
def get_console_ports(self, obj):
|
|
149
|
+
return PortSerializer(obj.consoleports.all(), many=True).data
|
|
150
|
+
|
|
151
|
+
def get_console_server_ports(self, obj):
|
|
152
|
+
return PortSerializer(obj.consoleserverports.all(), many=True).data
|
|
153
|
+
|
|
154
|
+
def get_module_bays(self, obj):
|
|
155
|
+
return ModuleBaySerializer(obj.modulebays.all(), many=True).data
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
class CableSerializer(serializers.ModelSerializer):
|
|
159
|
+
a_terminations = serializers.SerializerMethodField()
|
|
160
|
+
b_terminations = serializers.SerializerMethodField()
|
|
161
|
+
status_display = serializers.CharField(source='get_status_display', read_only=True)
|
|
162
|
+
type_display = serializers.CharField(source='get_type_display', read_only=True)
|
|
163
|
+
|
|
164
|
+
class Meta:
|
|
165
|
+
model = Cable
|
|
166
|
+
fields = [
|
|
167
|
+
'id', 'type', 'type_display', 'status', 'status_display',
|
|
168
|
+
'color', 'label', 'length', 'length_unit',
|
|
169
|
+
'a_terminations', 'b_terminations'
|
|
170
|
+
]
|
|
171
|
+
|
|
172
|
+
def get_a_terminations(self, obj):
|
|
173
|
+
return self._serialize_terminations(obj.a_terminations)
|
|
174
|
+
|
|
175
|
+
def get_b_terminations(self, obj):
|
|
176
|
+
return self._serialize_terminations(obj.b_terminations)
|
|
177
|
+
|
|
178
|
+
def _serialize_terminations(self, terminations):
|
|
179
|
+
result = []
|
|
180
|
+
for term in terminations:
|
|
181
|
+
data = {
|
|
182
|
+
'id': term.id,
|
|
183
|
+
'name': term.name if hasattr(term, 'name') else str(term),
|
|
184
|
+
'type': term.__class__.__name__,
|
|
185
|
+
}
|
|
186
|
+
if hasattr(term, 'device'):
|
|
187
|
+
data['device_id'] = term.device.id
|
|
188
|
+
data['device_name'] = term.device.name
|
|
189
|
+
result.append(data)
|
|
190
|
+
return result
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
# Defines which port types may be connected to which other port types.
|
|
194
|
+
# This mirrors NetBox's own cable termination compatibility rules.
|
|
195
|
+
COMPATIBLE_TERMINATION_TYPES = {
|
|
196
|
+
'Interface': {'Interface', 'FrontPort', 'RearPort'},
|
|
197
|
+
'FrontPort': {'Interface', 'FrontPort', 'RearPort', 'ConsolePort', 'ConsoleServerPort'},
|
|
198
|
+
'RearPort': {'Interface', 'FrontPort', 'RearPort', 'ConsolePort', 'ConsoleServerPort'},
|
|
199
|
+
'ConsolePort': {'ConsoleServerPort', 'FrontPort', 'RearPort'},
|
|
200
|
+
'ConsoleServerPort': {'ConsolePort', 'FrontPort', 'RearPort'},
|
|
201
|
+
'PowerPort': {'PowerOutlet'},
|
|
202
|
+
'PowerOutlet': {'PowerPort'},
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
class CableCreateSerializer(serializers.Serializer):
|
|
207
|
+
"""Serializer for creating cables via the patcher interface."""
|
|
208
|
+
a_termination_type = serializers.CharField()
|
|
209
|
+
a_termination_id = serializers.IntegerField()
|
|
210
|
+
b_termination_type = serializers.CharField()
|
|
211
|
+
b_termination_id = serializers.IntegerField()
|
|
212
|
+
type = serializers.CharField(required=False, allow_blank=True)
|
|
213
|
+
status = serializers.CharField(default='connected')
|
|
214
|
+
color = serializers.CharField(required=False, allow_blank=True)
|
|
215
|
+
label = serializers.CharField(required=False, allow_blank=True)
|
|
216
|
+
|
|
217
|
+
def validate(self, data):
|
|
218
|
+
a_type = data.get('a_termination_type')
|
|
219
|
+
b_type = data.get('b_termination_type')
|
|
220
|
+
|
|
221
|
+
valid_types = set(COMPATIBLE_TERMINATION_TYPES.keys())
|
|
222
|
+
|
|
223
|
+
if a_type not in valid_types:
|
|
224
|
+
raise serializers.ValidationError(
|
|
225
|
+
{'a_termination_type': f'Invalid termination type: {a_type}'}
|
|
226
|
+
)
|
|
227
|
+
if b_type not in valid_types:
|
|
228
|
+
raise serializers.ValidationError(
|
|
229
|
+
{'b_termination_type': f'Invalid termination type: {b_type}'}
|
|
230
|
+
)
|
|
231
|
+
|
|
232
|
+
allowed_b_types = COMPATIBLE_TERMINATION_TYPES.get(a_type, set())
|
|
233
|
+
if b_type not in allowed_b_types:
|
|
234
|
+
raise serializers.ValidationError(
|
|
235
|
+
f'Cannot connect a {a_type} to a {b_type}. '
|
|
236
|
+
f'A {a_type} may only connect to: {", ".join(sorted(allowed_b_types))}.'
|
|
237
|
+
)
|
|
238
|
+
|
|
239
|
+
return data
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
from django.urls import path, include
|
|
2
|
+
from rest_framework.routers import DefaultRouter
|
|
3
|
+
from . import views
|
|
4
|
+
|
|
5
|
+
router = DefaultRouter()
|
|
6
|
+
router.register('locations', views.LocationsViewSet, basename='locations')
|
|
7
|
+
router.register('devices', views.DevicesViewSet, basename='devices')
|
|
8
|
+
router.register('cables', views.CablesViewSet, basename='cables')
|
|
9
|
+
router.register('module-types', views.ModuleTypesViewSet, basename='module-types')
|
|
10
|
+
router.register('module-bays', views.ModuleBaysViewSet, basename='module-bays')
|
|
11
|
+
|
|
12
|
+
urlpatterns = router.urls
|