ipfabric_netbox 4.3.2b9__py3-none-any.whl → 4.3.2b11__py3-none-any.whl
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.
Potentially problematic release.
This version of ipfabric_netbox might be problematic. Click here for more details.
- ipfabric_netbox/__init__.py +1 -1
- ipfabric_netbox/api/serializers.py +112 -7
- ipfabric_netbox/api/urls.py +6 -0
- ipfabric_netbox/api/views.py +23 -0
- ipfabric_netbox/choices.py +74 -40
- ipfabric_netbox/data/endpoint.json +52 -0
- ipfabric_netbox/data/filters.json +51 -0
- ipfabric_netbox/data/transform_map.json +190 -176
- ipfabric_netbox/exceptions.py +7 -5
- ipfabric_netbox/filtersets.py +310 -41
- ipfabric_netbox/forms.py +330 -80
- ipfabric_netbox/graphql/__init__.py +6 -0
- ipfabric_netbox/graphql/enums.py +5 -5
- ipfabric_netbox/graphql/filters.py +56 -4
- ipfabric_netbox/graphql/schema.py +28 -0
- ipfabric_netbox/graphql/types.py +61 -1
- ipfabric_netbox/jobs.py +12 -1
- ipfabric_netbox/migrations/0022_prepare_for_filters.py +182 -0
- ipfabric_netbox/migrations/0023_populate_filters_data.py +303 -0
- ipfabric_netbox/migrations/0024_finish_filters.py +29 -0
- ipfabric_netbox/migrations/0025_add_vss_chassis_endpoint.py +166 -0
- ipfabric_netbox/models.py +432 -17
- ipfabric_netbox/navigation.py +98 -24
- ipfabric_netbox/tables.py +194 -9
- ipfabric_netbox/templates/ipfabric_netbox/htmx_list.html +5 -0
- ipfabric_netbox/templates/ipfabric_netbox/inc/combined_expressions.html +59 -0
- ipfabric_netbox/templates/ipfabric_netbox/inc/combined_expressions_content.html +39 -0
- ipfabric_netbox/templates/ipfabric_netbox/inc/endpoint_filters_with_selector.html +54 -0
- ipfabric_netbox/templates/ipfabric_netbox/ipfabricendpoint.html +39 -0
- ipfabric_netbox/templates/ipfabric_netbox/ipfabricfilter.html +51 -0
- ipfabric_netbox/templates/ipfabric_netbox/ipfabricfilterexpression.html +39 -0
- ipfabric_netbox/templates/ipfabric_netbox/ipfabricfilterexpression_edit.html +150 -0
- ipfabric_netbox/templates/ipfabric_netbox/ipfabricsync.html +1 -1
- ipfabric_netbox/templates/ipfabric_netbox/ipfabrictransformmap.html +16 -2
- ipfabric_netbox/templatetags/ipfabric_netbox_helpers.py +68 -0
- ipfabric_netbox/tests/api/test_api.py +333 -13
- ipfabric_netbox/tests/test_filtersets.py +2592 -0
- ipfabric_netbox/tests/test_forms.py +1349 -74
- ipfabric_netbox/tests/test_models.py +242 -34
- ipfabric_netbox/tests/test_views.py +2031 -26
- ipfabric_netbox/urls.py +35 -0
- ipfabric_netbox/utilities/endpoint.py +83 -0
- ipfabric_netbox/utilities/filters.py +88 -0
- ipfabric_netbox/utilities/ipfutils.py +393 -377
- ipfabric_netbox/utilities/logging.py +7 -7
- ipfabric_netbox/utilities/transform_map.py +144 -5
- ipfabric_netbox/views.py +719 -5
- {ipfabric_netbox-4.3.2b9.dist-info → ipfabric_netbox-4.3.2b11.dist-info}/METADATA +2 -2
- {ipfabric_netbox-4.3.2b9.dist-info → ipfabric_netbox-4.3.2b11.dist-info}/RECORD +50 -33
- {ipfabric_netbox-4.3.2b9.dist-info → ipfabric_netbox-4.3.2b11.dist-info}/WHEEL +1 -1
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
from typing import TYPE_CHECKING
|
|
2
|
+
|
|
3
|
+
from django.db import migrations
|
|
4
|
+
|
|
5
|
+
from ipfabric_netbox.utilities.endpoint import build_endpoints
|
|
6
|
+
from ipfabric_netbox.utilities.endpoint import get_endpoint_data
|
|
7
|
+
from ipfabric_netbox.utilities.filters import build_filters
|
|
8
|
+
from ipfabric_netbox.utilities.filters import get_filter_data
|
|
9
|
+
from ipfabric_netbox.utilities.transform_map import get_transform_map
|
|
10
|
+
|
|
11
|
+
if TYPE_CHECKING:
|
|
12
|
+
from django.apps import apps as apps_type
|
|
13
|
+
from django.db.backends.base.schema import BaseDatabaseSchemaEditor
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def prepare_endpoints(apps: "apps_type", schema_editor: "BaseDatabaseSchemaEditor"):
|
|
17
|
+
"""Create endpoints if they do not exist yet."""
|
|
18
|
+
build_endpoints(
|
|
19
|
+
data=get_endpoint_data(), apps=apps, db_alias=schema_editor.connection.alias
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def prepare_filters(apps: "apps_type", schema_editor: "BaseDatabaseSchemaEditor"):
|
|
24
|
+
"""Create filters if they do not exist yet."""
|
|
25
|
+
build_filters(
|
|
26
|
+
data=get_filter_data(), apps=apps, db_alias=schema_editor.connection.alias
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def assign_filters_to_syncs(
|
|
31
|
+
apps: "apps_type", schema_editor: "BaseDatabaseSchemaEditor"
|
|
32
|
+
):
|
|
33
|
+
"""Assign all created IPFabricFilters to all existing IPFabricSync objects."""
|
|
34
|
+
IPFabricSync = apps.get_model("ipfabric_netbox", "IPFabricSync")
|
|
35
|
+
IPFabricFilter = apps.get_model("ipfabric_netbox", "IPFabricFilter")
|
|
36
|
+
|
|
37
|
+
all_filters = IPFabricFilter.objects.using(schema_editor.connection.alias).all()
|
|
38
|
+
for sync in IPFabricSync.objects.using(schema_editor.connection.alias).all():
|
|
39
|
+
sync.filters.set(all_filters)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def migrate_source_model_to_endpoint(
|
|
43
|
+
apps: "apps_type", schema_editor: "BaseDatabaseSchemaEditor"
|
|
44
|
+
):
|
|
45
|
+
"""Migrate IPFabricTransformMap source_model data to source_endpoint."""
|
|
46
|
+
IPFabricTransformMap = apps.get_model("ipfabric_netbox", "IPFabricTransformMap")
|
|
47
|
+
IPFabricEndpoint = apps.get_model("ipfabric_netbox", "IPFabricEndpoint")
|
|
48
|
+
ContentType = apps.get_model("contenttypes", "ContentType")
|
|
49
|
+
|
|
50
|
+
source_model_to_endpoint = {
|
|
51
|
+
"site": "/inventory/sites/overview",
|
|
52
|
+
"device": "/inventory/devices",
|
|
53
|
+
"virtualchassis": "/technology/platforms/stack/members",
|
|
54
|
+
"interface": "/inventory/interfaces",
|
|
55
|
+
"part_number": "/inventory/part-numbers",
|
|
56
|
+
"vlan": "/technology/vlans/site-summary",
|
|
57
|
+
"vrf": "/technology/routing/vrf/detail",
|
|
58
|
+
"prefix": "/technology/networks/managed-networks",
|
|
59
|
+
"ipaddress": "/technology/addressing/managed-ip/ipv4",
|
|
60
|
+
"inventory": "/inventory/part-numbers",
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
# Get first endpoint as fallback to make sure it's always set
|
|
64
|
+
fallback_endpoint = IPFabricEndpoint.objects.using(
|
|
65
|
+
schema_editor.connection.alias
|
|
66
|
+
).first()
|
|
67
|
+
|
|
68
|
+
for transform_map in IPFabricTransformMap.objects.using(
|
|
69
|
+
schema_editor.connection.alias
|
|
70
|
+
).all():
|
|
71
|
+
endpoint_value = source_model_to_endpoint.get(transform_map.source_model)
|
|
72
|
+
|
|
73
|
+
# Special case: source_model="device" with target_model="dcim.virtualchassis"
|
|
74
|
+
# should map to /technology/platforms/stack/members, not /inventory/devices
|
|
75
|
+
if transform_map.source_model == "device" and transform_map.target_model:
|
|
76
|
+
virtualchassis_ct = ContentType.objects.using(
|
|
77
|
+
schema_editor.connection.alias
|
|
78
|
+
).get(app_label="dcim", model="virtualchassis")
|
|
79
|
+
if transform_map.target_model_id == virtualchassis_ct.id:
|
|
80
|
+
endpoint_value = "/technology/platforms/stack/members"
|
|
81
|
+
|
|
82
|
+
# If no mapping exists, use fallback and mark the name for manual fix
|
|
83
|
+
if not endpoint_value:
|
|
84
|
+
if not transform_map.name.startswith("[NEEDS CORRECTION]"):
|
|
85
|
+
transform_map.name = f"[NEEDS CORRECTION - Unknown source_model: {transform_map.source_model}] {transform_map.name}"
|
|
86
|
+
transform_map.source_endpoint = fallback_endpoint
|
|
87
|
+
transform_map.save()
|
|
88
|
+
continue
|
|
89
|
+
|
|
90
|
+
# Try to get the mapped endpoint
|
|
91
|
+
try:
|
|
92
|
+
endpoint = IPFabricEndpoint.objects.using(
|
|
93
|
+
schema_editor.connection.alias
|
|
94
|
+
).get(endpoint=endpoint_value)
|
|
95
|
+
transform_map.source_endpoint = endpoint
|
|
96
|
+
except IPFabricEndpoint.DoesNotExist:
|
|
97
|
+
# Use fallback endpoint and mark the name
|
|
98
|
+
if not transform_map.name.startswith("[NEEDS CORRECTION]"):
|
|
99
|
+
transform_map.name = f"[NEEDS CORRECTION - Expected endpoint '{endpoint_value}' not found] {transform_map.name}"
|
|
100
|
+
transform_map.source_endpoint = fallback_endpoint
|
|
101
|
+
transform_map.save()
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def migrate_endpoint_to_source_model(
|
|
105
|
+
apps: "apps_type", schema_editor: "BaseDatabaseSchemaEditor"
|
|
106
|
+
):
|
|
107
|
+
"""Reverse migration: migrate IPFabricTransformMap source_endpoint data back to source_model."""
|
|
108
|
+
IPFabricTransformMap = apps.get_model("ipfabric_netbox", "IPFabricTransformMap")
|
|
109
|
+
IPFabricEndpoint = apps.get_model("ipfabric_netbox", "IPFabricEndpoint")
|
|
110
|
+
ContentType = apps.get_model("contenttypes", "ContentType")
|
|
111
|
+
|
|
112
|
+
endpoint_to_source_model = {
|
|
113
|
+
"/inventory/sites/overview": "site",
|
|
114
|
+
"/inventory/devices": "device",
|
|
115
|
+
"/technology/platforms/stack/members": "virtualchassis",
|
|
116
|
+
"/inventory/interfaces": "interface",
|
|
117
|
+
"/inventory/part-numbers": "part_number",
|
|
118
|
+
"/technology/vlans/site-summary": "vlan",
|
|
119
|
+
"/technology/routing/vrf/detail": "vrf",
|
|
120
|
+
"/technology/networks/managed-networks": "prefix",
|
|
121
|
+
"/technology/addressing/managed-ip/ipv4": "ipaddress",
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
for transform_map in IPFabricTransformMap.objects.using(
|
|
125
|
+
schema_editor.connection.alias
|
|
126
|
+
).all():
|
|
127
|
+
if not transform_map.source_endpoint:
|
|
128
|
+
continue
|
|
129
|
+
|
|
130
|
+
# Get the endpoint and map it back to source_model
|
|
131
|
+
try:
|
|
132
|
+
endpoint = IPFabricEndpoint.objects.using(
|
|
133
|
+
schema_editor.connection.alias
|
|
134
|
+
).get(pk=transform_map.source_endpoint_id)
|
|
135
|
+
source_model_value = endpoint_to_source_model.get(endpoint.endpoint)
|
|
136
|
+
|
|
137
|
+
# Special case: source_endpoint="/technology/platforms/stack/members" with
|
|
138
|
+
# target_model="dcim.virtualchassis" should map back to source_model="device"
|
|
139
|
+
if (
|
|
140
|
+
endpoint.endpoint == "/technology/platforms/stack/members"
|
|
141
|
+
and transform_map.target_model
|
|
142
|
+
):
|
|
143
|
+
virtualchassis_ct = ContentType.objects.using(
|
|
144
|
+
schema_editor.connection.alias
|
|
145
|
+
).get(app_label="dcim", model="virtualchassis")
|
|
146
|
+
if transform_map.target_model_id == virtualchassis_ct.id:
|
|
147
|
+
source_model_value = "device"
|
|
148
|
+
|
|
149
|
+
transform_map.source_model = source_model_value
|
|
150
|
+
except IPFabricEndpoint.DoesNotExist:
|
|
151
|
+
pass
|
|
152
|
+
|
|
153
|
+
# Restore original name by removing correction markers
|
|
154
|
+
if transform_map.name.startswith("[NEEDS CORRECTION"):
|
|
155
|
+
# Extract original name after the correction marker
|
|
156
|
+
import re
|
|
157
|
+
|
|
158
|
+
match = re.match(r"\[NEEDS CORRECTION[^]]*]\s*(.+)", transform_map.name)
|
|
159
|
+
if match:
|
|
160
|
+
transform_map.name = match.group(1)
|
|
161
|
+
transform_map.save()
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
transform_keys = {
|
|
165
|
+
"site": "dcim.site",
|
|
166
|
+
"vlan": "ipam.vlan",
|
|
167
|
+
"manufacturer": "dcim.manufacturer",
|
|
168
|
+
"vrf": "ipam.vrf",
|
|
169
|
+
"platform": "dcim.platform",
|
|
170
|
+
"devicerole": "dcim.devicerole",
|
|
171
|
+
"devicetype": "dcim.devicetype",
|
|
172
|
+
"prefix": "ipam.prefix",
|
|
173
|
+
"device": "dcim.device",
|
|
174
|
+
"virtualchassis": "dcim.virtualchassis",
|
|
175
|
+
"ipaddress": "ipam.ipaddress",
|
|
176
|
+
"interface": "dcim.interface",
|
|
177
|
+
"inventoryitem": "dcim.inventoryitem",
|
|
178
|
+
"macaddress": "dcim.macaddress",
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
def correct_sync_parameters(
|
|
183
|
+
apps: "apps_type", schema_editor: "BaseDatabaseSchemaEditor"
|
|
184
|
+
):
|
|
185
|
+
"""Correct existing sync parameters to match new structure."""
|
|
186
|
+
IPFabricSync = apps.get_model("ipfabric_netbox", "IPFabricSync")
|
|
187
|
+
for sync in IPFabricSync.objects.using(schema_editor.connection.alias).all():
|
|
188
|
+
for old_key, new_key in transform_keys.items():
|
|
189
|
+
if old_key not in sync.parameters:
|
|
190
|
+
continue
|
|
191
|
+
sync.parameters[new_key] = sync.parameters.pop(old_key)
|
|
192
|
+
sync.save()
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
def return_sync_parameters(
|
|
196
|
+
apps: "apps_type", schema_editor: "BaseDatabaseSchemaEditor"
|
|
197
|
+
):
|
|
198
|
+
"""Reverse the key renaming in sync parameters to restore original structure."""
|
|
199
|
+
IPFabricSync = apps.get_model("ipfabric_netbox", "IPFabricSync")
|
|
200
|
+
reverse_transform_keys = {v: k for k, v in transform_keys.items()}
|
|
201
|
+
for sync in IPFabricSync.objects.using(schema_editor.connection.alias).all():
|
|
202
|
+
for new_key, old_key in reverse_transform_keys.items():
|
|
203
|
+
if new_key not in sync.parameters:
|
|
204
|
+
continue
|
|
205
|
+
sync.parameters[old_key] = sync.parameters.pop(new_key)
|
|
206
|
+
sync.save()
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
def assign_parent_transform_maps(
|
|
210
|
+
apps: "apps_type", schema_editor: "BaseDatabaseSchemaEditor"
|
|
211
|
+
):
|
|
212
|
+
"""Assign parent relationships to transform maps based on transform_map.json data."""
|
|
213
|
+
|
|
214
|
+
IPFabricTransformMap = apps.get_model("ipfabric_netbox", "IPFabricTransformMap")
|
|
215
|
+
ContentType = apps.get_model("contenttypes", "ContentType")
|
|
216
|
+
transform_map_data = get_transform_map()
|
|
217
|
+
|
|
218
|
+
# Process each transform map from JSON
|
|
219
|
+
for tm_data in transform_map_data:
|
|
220
|
+
data = tm_data.get("data", {})
|
|
221
|
+
|
|
222
|
+
parents_value = data.get("parents")
|
|
223
|
+
if not parents_value:
|
|
224
|
+
continue
|
|
225
|
+
|
|
226
|
+
# Handle both string (single parent) and array (multiple parents) formats
|
|
227
|
+
if isinstance(parents_value, str):
|
|
228
|
+
parents_value = [parents_value]
|
|
229
|
+
|
|
230
|
+
try:
|
|
231
|
+
# Find the transform map (without group) matching this target model
|
|
232
|
+
target_model_data = data.get("target_model", {})
|
|
233
|
+
target_model = ContentType.objects.using(
|
|
234
|
+
schema_editor.connection.alias
|
|
235
|
+
).get(
|
|
236
|
+
app_label=target_model_data.get("app_label"),
|
|
237
|
+
model=target_model_data.get("model"),
|
|
238
|
+
)
|
|
239
|
+
transform_map = (
|
|
240
|
+
IPFabricTransformMap.objects.using(schema_editor.connection.alias)
|
|
241
|
+
.filter(group__isnull=True, target_model=target_model)
|
|
242
|
+
.first()
|
|
243
|
+
)
|
|
244
|
+
if not transform_map:
|
|
245
|
+
continue
|
|
246
|
+
|
|
247
|
+
# Add each parent to the M2M relationship
|
|
248
|
+
for parent_str in parents_value:
|
|
249
|
+
parent_app_label, parent_model = parent_str.split(".")
|
|
250
|
+
|
|
251
|
+
try:
|
|
252
|
+
parent_content_type = ContentType.objects.using(
|
|
253
|
+
schema_editor.connection.alias
|
|
254
|
+
).get(app_label=parent_app_label, model=parent_model)
|
|
255
|
+
parent_transform_map = (
|
|
256
|
+
IPFabricTransformMap.objects.using(
|
|
257
|
+
schema_editor.connection.alias
|
|
258
|
+
)
|
|
259
|
+
.filter(group__isnull=True, target_model=parent_content_type)
|
|
260
|
+
.first()
|
|
261
|
+
)
|
|
262
|
+
if parent_transform_map:
|
|
263
|
+
transform_map.parents.add(parent_transform_map)
|
|
264
|
+
|
|
265
|
+
except ContentType.DoesNotExist:
|
|
266
|
+
continue
|
|
267
|
+
|
|
268
|
+
except ContentType.DoesNotExist:
|
|
269
|
+
continue
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
class Migration(migrations.Migration):
|
|
273
|
+
dependencies = [
|
|
274
|
+
("extras", "0132_configcontextprofile"),
|
|
275
|
+
("ipfabric_netbox", "0022_prepare_for_filters"),
|
|
276
|
+
]
|
|
277
|
+
|
|
278
|
+
operations = [
|
|
279
|
+
migrations.RunPython(
|
|
280
|
+
prepare_endpoints,
|
|
281
|
+
migrations.RunPython.noop,
|
|
282
|
+
),
|
|
283
|
+
migrations.RunPython(
|
|
284
|
+
prepare_filters,
|
|
285
|
+
migrations.RunPython.noop,
|
|
286
|
+
),
|
|
287
|
+
migrations.RunPython(
|
|
288
|
+
assign_filters_to_syncs,
|
|
289
|
+
migrations.RunPython.noop,
|
|
290
|
+
),
|
|
291
|
+
migrations.RunPython(
|
|
292
|
+
migrate_source_model_to_endpoint,
|
|
293
|
+
migrate_endpoint_to_source_model,
|
|
294
|
+
),
|
|
295
|
+
migrations.RunPython(
|
|
296
|
+
correct_sync_parameters,
|
|
297
|
+
return_sync_parameters,
|
|
298
|
+
),
|
|
299
|
+
migrations.RunPython(
|
|
300
|
+
assign_parent_transform_maps,
|
|
301
|
+
migrations.RunPython.noop,
|
|
302
|
+
),
|
|
303
|
+
]
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# Generated by Django 5.2.5 on 2025-12-02 12:06
|
|
2
|
+
import django.db.models.deletion
|
|
3
|
+
from django.db import migrations
|
|
4
|
+
from django.db import models
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class Migration(migrations.Migration):
|
|
8
|
+
dependencies = [
|
|
9
|
+
("extras", "0132_configcontextprofile"),
|
|
10
|
+
("ipfabric_netbox", "0023_populate_filters_data"),
|
|
11
|
+
]
|
|
12
|
+
|
|
13
|
+
operations = [
|
|
14
|
+
# The new endpoint is populated, so we can make the field non-nullable
|
|
15
|
+
migrations.AlterField(
|
|
16
|
+
model_name="ipfabrictransformmap",
|
|
17
|
+
name="source_endpoint",
|
|
18
|
+
field=models.ForeignKey(
|
|
19
|
+
on_delete=django.db.models.deletion.PROTECT,
|
|
20
|
+
related_name="transform_maps",
|
|
21
|
+
to="ipfabric_netbox.ipfabricendpoint",
|
|
22
|
+
),
|
|
23
|
+
),
|
|
24
|
+
# Remove the old source_model field as it's been replaced by source_endpoint
|
|
25
|
+
migrations.RemoveField(
|
|
26
|
+
model_name="ipfabrictransformmap",
|
|
27
|
+
name="source_model",
|
|
28
|
+
),
|
|
29
|
+
]
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
from typing import TYPE_CHECKING
|
|
2
|
+
|
|
3
|
+
from django.db import migrations
|
|
4
|
+
|
|
5
|
+
from ipfabric_netbox.utilities.endpoint import do_endpoint_change
|
|
6
|
+
from ipfabric_netbox.utilities.endpoint import EndpointRecord
|
|
7
|
+
from ipfabric_netbox.utilities.transform_map import do_change
|
|
8
|
+
from ipfabric_netbox.utilities.transform_map import FieldRecord
|
|
9
|
+
from ipfabric_netbox.utilities.transform_map import RelationshipRecord
|
|
10
|
+
from ipfabric_netbox.utilities.transform_map import TransformMapRecord
|
|
11
|
+
|
|
12
|
+
if TYPE_CHECKING:
|
|
13
|
+
from django.apps import apps as apps_type
|
|
14
|
+
from django.db.backends.base.schema import BaseDatabaseSchemaEditor
|
|
15
|
+
|
|
16
|
+
ENDPOINTS = (
|
|
17
|
+
EndpointRecord(
|
|
18
|
+
name="Default VSS Chassis Endpoint",
|
|
19
|
+
description="",
|
|
20
|
+
endpoint="/technology/platforms/vss/chassis",
|
|
21
|
+
),
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
TRANSFORM_MAP_CHANGES = (
|
|
25
|
+
TransformMapRecord(
|
|
26
|
+
source_endpoint="/technology/platforms/stack/members",
|
|
27
|
+
target_model="dcim.virtualchassis",
|
|
28
|
+
fields=(
|
|
29
|
+
FieldRecord(
|
|
30
|
+
source_field="master",
|
|
31
|
+
target_field="name",
|
|
32
|
+
old_template="",
|
|
33
|
+
new_template="{% if object.master is defined and object.master %}{{ object.master }}{% else %}{{ object.hostname }}{% endif %}",
|
|
34
|
+
),
|
|
35
|
+
),
|
|
36
|
+
),
|
|
37
|
+
TransformMapRecord(
|
|
38
|
+
source_endpoint="/inventory/devices",
|
|
39
|
+
target_model="dcim.device",
|
|
40
|
+
fields=(
|
|
41
|
+
FieldRecord(
|
|
42
|
+
source_field="hostname",
|
|
43
|
+
target_field="vc_position",
|
|
44
|
+
old_template="{% if object.virtual_chassis %}{{ object.virtual_chassis.member }}{% else %}None{% endif %}",
|
|
45
|
+
new_template="{% if object.virtual_chassis %}{% if object.virtual_chassis.member is defined and object.virtual_chassis.member %}{{ object.virtual_chassis.member }}{% else %}{{ object.virtual_chassis.chassisId }}{% endif %}{% else %}None{% endif %}",
|
|
46
|
+
),
|
|
47
|
+
),
|
|
48
|
+
relationships=(
|
|
49
|
+
RelationshipRecord(
|
|
50
|
+
source_model="dcim.virtualchassis",
|
|
51
|
+
target_field="virtual_chassis",
|
|
52
|
+
old_template="{% if object.virtual_chassis %}{{ dcim.VirtualChassis.objects.filter(name=object.virtual_chassis.master).first().pk }}{% endif %}",
|
|
53
|
+
new_template="{% if object.virtual_chassis %}{% if object.virtual_chassis.master is defined and object.virtual_chassis.master %}{{ dcim.VirtualChassis.objects.filter(name=object.virtual_chassis.master).first().pk }}{% else %}{{ dcim.VirtualChassis.objects.filter(name=object.virtual_chassis.hostname).first().pk }}{% endif %}{% endif %}",
|
|
54
|
+
),
|
|
55
|
+
),
|
|
56
|
+
),
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def add_vss_chassis_endpoint(
|
|
61
|
+
apps: "apps_type", schema_editor: "BaseDatabaseSchemaEditor"
|
|
62
|
+
):
|
|
63
|
+
"""Add VSS chassis endpoint."""
|
|
64
|
+
do_endpoint_change(apps, schema_editor, ENDPOINTS, forward=True)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def remove_vss_chassis_endpoint(
|
|
68
|
+
apps: "apps_type", schema_editor: "BaseDatabaseSchemaEditor"
|
|
69
|
+
):
|
|
70
|
+
"""Remove VSS chassis endpoint."""
|
|
71
|
+
do_endpoint_change(apps, schema_editor, ENDPOINTS, forward=False)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def forward_transform_maps_change(
|
|
75
|
+
apps: "apps_type", schema_editor: "BaseDatabaseSchemaEditor"
|
|
76
|
+
):
|
|
77
|
+
"""Replace old template with updated version."""
|
|
78
|
+
do_change(apps, schema_editor, changes=TRANSFORM_MAP_CHANGES, forward=True)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def revert_transform_maps_change(
|
|
82
|
+
apps: "apps_type", schema_editor: "BaseDatabaseSchemaEditor"
|
|
83
|
+
):
|
|
84
|
+
"""Revert template back to the previous exact template."""
|
|
85
|
+
do_change(apps, schema_editor, changes=TRANSFORM_MAP_CHANGES, forward=False)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def add_vss_to_child_filter(
|
|
89
|
+
apps: "apps_type", schema_editor: "BaseDatabaseSchemaEditor"
|
|
90
|
+
):
|
|
91
|
+
"""Add VSS chassis endpoint to Default Child Devices Filter idempotently."""
|
|
92
|
+
IPFabricFilter = apps.get_model("ipfabric_netbox", "IPFabricFilter")
|
|
93
|
+
IPFabricEndpoint = apps.get_model("ipfabric_netbox", "IPFabricEndpoint")
|
|
94
|
+
|
|
95
|
+
# Get the filter
|
|
96
|
+
try:
|
|
97
|
+
child_filter = IPFabricFilter.objects.using(schema_editor.connection.alias).get(
|
|
98
|
+
name="Default Child Devices Filter"
|
|
99
|
+
)
|
|
100
|
+
except IPFabricFilter.DoesNotExist:
|
|
101
|
+
# Filter doesn't exist, nothing to update
|
|
102
|
+
return
|
|
103
|
+
|
|
104
|
+
# Get the VSS chassis endpoint
|
|
105
|
+
try:
|
|
106
|
+
vss_endpoint = IPFabricEndpoint.objects.using(
|
|
107
|
+
schema_editor.connection.alias
|
|
108
|
+
).get(endpoint="/technology/platforms/vss/chassis")
|
|
109
|
+
except IPFabricEndpoint.DoesNotExist:
|
|
110
|
+
# Endpoint doesn't exist yet, skip (will be created by add_vss_chassis_endpoint)
|
|
111
|
+
return
|
|
112
|
+
|
|
113
|
+
# Add the endpoint to the filter if not already present
|
|
114
|
+
if not child_filter.endpoints.filter(pk=vss_endpoint.pk).exists():
|
|
115
|
+
child_filter.endpoints.add(vss_endpoint)
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def remove_vss_from_child_filter(
|
|
119
|
+
apps: "apps_type", schema_editor: "BaseDatabaseSchemaEditor"
|
|
120
|
+
):
|
|
121
|
+
"""Remove VSS chassis endpoint from Default Child Devices Filter."""
|
|
122
|
+
IPFabricFilter = apps.get_model("ipfabric_netbox", "IPFabricFilter")
|
|
123
|
+
IPFabricEndpoint = apps.get_model("ipfabric_netbox", "IPFabricEndpoint")
|
|
124
|
+
|
|
125
|
+
# Get the filter
|
|
126
|
+
try:
|
|
127
|
+
child_filter = IPFabricFilter.objects.using(schema_editor.connection.alias).get(
|
|
128
|
+
name="Default Child Devices Filter"
|
|
129
|
+
)
|
|
130
|
+
except IPFabricFilter.DoesNotExist:
|
|
131
|
+
# Filter doesn't exist, nothing to update
|
|
132
|
+
return
|
|
133
|
+
|
|
134
|
+
# Get the VSS chassis endpoint
|
|
135
|
+
try:
|
|
136
|
+
vss_endpoint = IPFabricEndpoint.objects.using(
|
|
137
|
+
schema_editor.connection.alias
|
|
138
|
+
).get(endpoint="/technology/platforms/vss/chassis")
|
|
139
|
+
except IPFabricEndpoint.DoesNotExist:
|
|
140
|
+
# Endpoint doesn't exist, nothing to remove
|
|
141
|
+
return
|
|
142
|
+
|
|
143
|
+
# Remove the endpoint from the filter if present
|
|
144
|
+
if child_filter.endpoints.filter(pk=vss_endpoint.pk).exists():
|
|
145
|
+
child_filter.endpoints.remove(vss_endpoint)
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
class Migration(migrations.Migration):
|
|
149
|
+
dependencies = [
|
|
150
|
+
("ipfabric_netbox", "0024_finish_filters"),
|
|
151
|
+
]
|
|
152
|
+
|
|
153
|
+
operations = [
|
|
154
|
+
migrations.RunPython(
|
|
155
|
+
add_vss_chassis_endpoint,
|
|
156
|
+
remove_vss_chassis_endpoint,
|
|
157
|
+
),
|
|
158
|
+
migrations.RunPython(
|
|
159
|
+
forward_transform_maps_change,
|
|
160
|
+
revert_transform_maps_change,
|
|
161
|
+
),
|
|
162
|
+
migrations.RunPython(
|
|
163
|
+
add_vss_to_child_filter,
|
|
164
|
+
remove_vss_from_child_filter,
|
|
165
|
+
),
|
|
166
|
+
]
|