ipfabric_netbox 4.3.2b9__py3-none-any.whl → 4.3.2b10__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.

Files changed (49) hide show
  1. ipfabric_netbox/__init__.py +2 -2
  2. ipfabric_netbox/api/serializers.py +112 -7
  3. ipfabric_netbox/api/urls.py +6 -0
  4. ipfabric_netbox/api/views.py +23 -0
  5. ipfabric_netbox/choices.py +72 -40
  6. ipfabric_netbox/data/endpoint.json +47 -0
  7. ipfabric_netbox/data/filters.json +51 -0
  8. ipfabric_netbox/data/transform_map.json +188 -174
  9. ipfabric_netbox/exceptions.py +7 -5
  10. ipfabric_netbox/filtersets.py +310 -41
  11. ipfabric_netbox/forms.py +324 -79
  12. ipfabric_netbox/graphql/__init__.py +6 -0
  13. ipfabric_netbox/graphql/enums.py +5 -5
  14. ipfabric_netbox/graphql/filters.py +56 -4
  15. ipfabric_netbox/graphql/schema.py +28 -0
  16. ipfabric_netbox/graphql/types.py +61 -1
  17. ipfabric_netbox/jobs.py +5 -1
  18. ipfabric_netbox/migrations/0022_prepare_for_filters.py +182 -0
  19. ipfabric_netbox/migrations/0023_populate_filters_data.py +279 -0
  20. ipfabric_netbox/migrations/0024_finish_filters.py +29 -0
  21. ipfabric_netbox/models.py +384 -12
  22. ipfabric_netbox/navigation.py +98 -24
  23. ipfabric_netbox/tables.py +194 -9
  24. ipfabric_netbox/templates/ipfabric_netbox/htmx_list.html +5 -0
  25. ipfabric_netbox/templates/ipfabric_netbox/inc/combined_expressions.html +59 -0
  26. ipfabric_netbox/templates/ipfabric_netbox/inc/combined_expressions_content.html +39 -0
  27. ipfabric_netbox/templates/ipfabric_netbox/inc/endpoint_filters_with_selector.html +54 -0
  28. ipfabric_netbox/templates/ipfabric_netbox/ipfabricendpoint.html +39 -0
  29. ipfabric_netbox/templates/ipfabric_netbox/ipfabricfilter.html +51 -0
  30. ipfabric_netbox/templates/ipfabric_netbox/ipfabricfilterexpression.html +39 -0
  31. ipfabric_netbox/templates/ipfabric_netbox/ipfabricfilterexpression_edit.html +150 -0
  32. ipfabric_netbox/templates/ipfabric_netbox/ipfabricsync.html +1 -1
  33. ipfabric_netbox/templates/ipfabric_netbox/ipfabrictransformmap.html +16 -2
  34. ipfabric_netbox/templatetags/ipfabric_netbox_helpers.py +65 -0
  35. ipfabric_netbox/tests/api/test_api.py +333 -13
  36. ipfabric_netbox/tests/test_filtersets.py +2592 -0
  37. ipfabric_netbox/tests/test_forms.py +1256 -74
  38. ipfabric_netbox/tests/test_models.py +242 -34
  39. ipfabric_netbox/tests/test_views.py +2030 -25
  40. ipfabric_netbox/urls.py +35 -0
  41. ipfabric_netbox/utilities/endpoint.py +30 -0
  42. ipfabric_netbox/utilities/filters.py +88 -0
  43. ipfabric_netbox/utilities/ipfutils.py +254 -316
  44. ipfabric_netbox/utilities/logging.py +7 -7
  45. ipfabric_netbox/utilities/transform_map.py +126 -0
  46. ipfabric_netbox/views.py +719 -5
  47. {ipfabric_netbox-4.3.2b9.dist-info → ipfabric_netbox-4.3.2b10.dist-info}/METADATA +3 -2
  48. {ipfabric_netbox-4.3.2b9.dist-info → ipfabric_netbox-4.3.2b10.dist-info}/RECORD +49 -33
  49. {ipfabric_netbox-4.3.2b9.dist-info → ipfabric_netbox-4.3.2b10.dist-info}/WHEEL +1 -1
@@ -0,0 +1,279 @@
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
+
49
+ source_model_to_endpoint = {
50
+ "site": "/inventory/sites/overview",
51
+ "device": "/inventory/devices",
52
+ "virtualchassis": "/technology/platforms/stack/members",
53
+ "interface": "/inventory/interfaces",
54
+ "part_number": "/inventory/part-numbers",
55
+ "vlan": "/technology/vlans/site-summary",
56
+ "vrf": "/technology/routing/vrf/detail",
57
+ "prefix": "/technology/networks/managed-networks",
58
+ "ipaddress": "/technology/addressing/managed-ip/ipv4",
59
+ "inventory": "/inventory/part-numbers",
60
+ }
61
+
62
+ # Get first endpoint as fallback to make sure it's always set
63
+ fallback_endpoint = IPFabricEndpoint.objects.using(
64
+ schema_editor.connection.alias
65
+ ).first()
66
+
67
+ for transform_map in IPFabricTransformMap.objects.using(
68
+ schema_editor.connection.alias
69
+ ).all():
70
+ endpoint_value = source_model_to_endpoint.get(transform_map.source_model)
71
+
72
+ # If no mapping exists, use fallback and mark the name for manual fix
73
+ if not endpoint_value:
74
+ if not transform_map.name.startswith("[NEEDS CORRECTION]"):
75
+ transform_map.name = f"[NEEDS CORRECTION - Unknown source_model: {transform_map.source_model}] {transform_map.name}"
76
+ transform_map.source_endpoint = fallback_endpoint
77
+ transform_map.save()
78
+ continue
79
+
80
+ # Try to get the mapped endpoint
81
+ try:
82
+ endpoint = IPFabricEndpoint.objects.using(
83
+ schema_editor.connection.alias
84
+ ).get(endpoint=endpoint_value)
85
+ transform_map.source_endpoint = endpoint
86
+ except IPFabricEndpoint.DoesNotExist:
87
+ # Use fallback endpoint and mark the name
88
+ if not transform_map.name.startswith("[NEEDS CORRECTION]"):
89
+ transform_map.name = f"[NEEDS CORRECTION - Expected endpoint '{endpoint_value}' not found] {transform_map.name}"
90
+ transform_map.source_endpoint = fallback_endpoint
91
+ transform_map.save()
92
+
93
+
94
+ def migrate_endpoint_to_source_model(
95
+ apps: "apps_type", schema_editor: "BaseDatabaseSchemaEditor"
96
+ ):
97
+ """Reverse migration: migrate IPFabricTransformMap source_endpoint data back to source_model."""
98
+ IPFabricTransformMap = apps.get_model("ipfabric_netbox", "IPFabricTransformMap")
99
+ IPFabricEndpoint = apps.get_model("ipfabric_netbox", "IPFabricEndpoint")
100
+
101
+ endpoint_to_source_model = {
102
+ "/inventory/sites/overview": "site",
103
+ "/inventory/devices": "device",
104
+ "/technology/platforms/stack/members": "virtualchassis",
105
+ "/inventory/interfaces": "interface",
106
+ "/inventory/part-numbers": "part_number",
107
+ "/technology/vlans/site-summary": "vlan",
108
+ "/technology/routing/vrf/detail": "vrf",
109
+ "/technology/networks/managed-networks": "prefix",
110
+ "/technology/addressing/managed-ip/ipv4": "ipaddress",
111
+ }
112
+
113
+ for transform_map in IPFabricTransformMap.objects.using(
114
+ schema_editor.connection.alias
115
+ ).all():
116
+ if not transform_map.source_endpoint:
117
+ continue
118
+
119
+ # Get the endpoint and map it back to source_model
120
+ try:
121
+ endpoint = IPFabricEndpoint.objects.using(
122
+ schema_editor.connection.alias
123
+ ).get(pk=transform_map.source_endpoint_id)
124
+ source_model_value = endpoint_to_source_model.get(endpoint.endpoint)
125
+ transform_map.source_model = source_model_value
126
+ except IPFabricEndpoint.DoesNotExist:
127
+ pass
128
+
129
+ # Restore original name by removing correction markers
130
+ if transform_map.name.startswith("[NEEDS CORRECTION"):
131
+ # Extract original name after the correction marker
132
+ import re
133
+
134
+ match = re.match(r"\[NEEDS CORRECTION[^]]*]\s*(.+)", transform_map.name)
135
+ if match:
136
+ transform_map.name = match.group(1)
137
+ transform_map.save()
138
+
139
+
140
+ transform_keys = {
141
+ "site": "dcim.site",
142
+ "vlan": "ipam.vlan",
143
+ "manufacturer": "dcim.manufacturer",
144
+ "vrf": "ipam.vrf",
145
+ "platform": "dcim.platform",
146
+ "devicerole": "dcim.devicerole",
147
+ "devicetype": "dcim.devicetype",
148
+ "prefix": "ipam.prefix",
149
+ "device": "dcim.device",
150
+ "virtualchassis": "dcim.virtualchassis",
151
+ "ipaddress": "ipam.ipaddress",
152
+ "interface": "dcim.interface",
153
+ "inventoryitem": "dcim.inventoryitem",
154
+ "macaddress": "dcim.macaddress",
155
+ }
156
+
157
+
158
+ def correct_sync_parameters(
159
+ apps: "apps_type", schema_editor: "BaseDatabaseSchemaEditor"
160
+ ):
161
+ """Correct existing sync parameters to match new structure."""
162
+ IPFabricSync = apps.get_model("ipfabric_netbox", "IPFabricSync")
163
+ for sync in IPFabricSync.objects.using(schema_editor.connection.alias).all():
164
+ for old_key, new_key in transform_keys.items():
165
+ if old_key not in sync.parameters:
166
+ continue
167
+ sync.parameters[new_key] = sync.parameters.pop(old_key)
168
+ sync.save()
169
+
170
+
171
+ def return_sync_parameters(
172
+ apps: "apps_type", schema_editor: "BaseDatabaseSchemaEditor"
173
+ ):
174
+ """Reverse the key renaming in sync parameters to restore original structure."""
175
+ IPFabricSync = apps.get_model("ipfabric_netbox", "IPFabricSync")
176
+ reverse_transform_keys = {v: k for k, v in transform_keys.items()}
177
+ for sync in IPFabricSync.objects.using(schema_editor.connection.alias).all():
178
+ for new_key, old_key in reverse_transform_keys.items():
179
+ if new_key not in sync.parameters:
180
+ continue
181
+ sync.parameters[old_key] = sync.parameters.pop(new_key)
182
+ sync.save()
183
+
184
+
185
+ def assign_parent_transform_maps(
186
+ apps: "apps_type", schema_editor: "BaseDatabaseSchemaEditor"
187
+ ):
188
+ """Assign parent relationships to transform maps based on transform_map.json data."""
189
+
190
+ IPFabricTransformMap = apps.get_model("ipfabric_netbox", "IPFabricTransformMap")
191
+ ContentType = apps.get_model("contenttypes", "ContentType")
192
+ transform_map_data = get_transform_map()
193
+
194
+ # Process each transform map from JSON
195
+ for tm_data in transform_map_data:
196
+ data = tm_data.get("data", {})
197
+
198
+ parents_value = data.get("parents")
199
+ if not parents_value:
200
+ continue
201
+
202
+ # Handle both string (single parent) and array (multiple parents) formats
203
+ if isinstance(parents_value, str):
204
+ parents_value = [parents_value]
205
+
206
+ try:
207
+ # Find the transform map (without group) matching this target model
208
+ target_model_data = data.get("target_model", {})
209
+ target_model = ContentType.objects.using(
210
+ schema_editor.connection.alias
211
+ ).get(
212
+ app_label=target_model_data.get("app_label"),
213
+ model=target_model_data.get("model"),
214
+ )
215
+ transform_map = (
216
+ IPFabricTransformMap.objects.using(schema_editor.connection.alias)
217
+ .filter(group__isnull=True, target_model=target_model)
218
+ .first()
219
+ )
220
+ if not transform_map:
221
+ continue
222
+
223
+ # Add each parent to the M2M relationship
224
+ for parent_str in parents_value:
225
+ parent_app_label, parent_model = parent_str.split(".")
226
+
227
+ try:
228
+ parent_content_type = ContentType.objects.using(
229
+ schema_editor.connection.alias
230
+ ).get(app_label=parent_app_label, model=parent_model)
231
+ parent_transform_map = (
232
+ IPFabricTransformMap.objects.using(
233
+ schema_editor.connection.alias
234
+ )
235
+ .filter(group__isnull=True, target_model=parent_content_type)
236
+ .first()
237
+ )
238
+ if parent_transform_map:
239
+ transform_map.parents.add(parent_transform_map)
240
+
241
+ except ContentType.DoesNotExist:
242
+ continue
243
+
244
+ except ContentType.DoesNotExist:
245
+ continue
246
+
247
+
248
+ class Migration(migrations.Migration):
249
+ dependencies = [
250
+ ("extras", "0132_configcontextprofile"),
251
+ ("ipfabric_netbox", "0022_prepare_for_filters"),
252
+ ]
253
+
254
+ operations = [
255
+ migrations.RunPython(
256
+ prepare_endpoints,
257
+ migrations.RunPython.noop,
258
+ ),
259
+ migrations.RunPython(
260
+ prepare_filters,
261
+ migrations.RunPython.noop,
262
+ ),
263
+ migrations.RunPython(
264
+ assign_filters_to_syncs,
265
+ migrations.RunPython.noop,
266
+ ),
267
+ migrations.RunPython(
268
+ migrate_source_model_to_endpoint,
269
+ migrate_endpoint_to_source_model,
270
+ ),
271
+ migrations.RunPython(
272
+ correct_sync_parameters,
273
+ return_sync_parameters,
274
+ ),
275
+ migrations.RunPython(
276
+ assign_parent_transform_maps,
277
+ migrations.RunPython.noop,
278
+ ),
279
+ ]
@@ -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
+ ]