ipfabric_netbox 4.2.2b2__tar.gz → 4.2.2b4__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.
Potentially problematic release.
This version of ipfabric_netbox might be problematic. Click here for more details.
- {ipfabric_netbox-4.2.2b2 → ipfabric_netbox-4.2.2b4}/PKG-INFO +5 -3
- {ipfabric_netbox-4.2.2b2 → ipfabric_netbox-4.2.2b4}/README.md +3 -1
- {ipfabric_netbox-4.2.2b2 → ipfabric_netbox-4.2.2b4}/ipfabric_netbox/__init__.py +2 -2
- {ipfabric_netbox-4.2.2b2 → ipfabric_netbox-4.2.2b4}/ipfabric_netbox/forms.py +84 -21
- {ipfabric_netbox-4.2.2b2 → ipfabric_netbox-4.2.2b4}/ipfabric_netbox/migrations/0001_initial_squashed_0013_switch_to_branching_plugin.py +37 -17
- ipfabric_netbox-4.2.2b4/ipfabric_netbox/migrations/0019_alter_ipfabrictransformmap_options_and_more.py +19 -0
- {ipfabric_netbox-4.2.2b2 → ipfabric_netbox-4.2.2b4}/ipfabric_netbox/models.py +30 -6
- {ipfabric_netbox-4.2.2b2 → ipfabric_netbox-4.2.2b4}/ipfabric_netbox/template_content.py +9 -6
- {ipfabric_netbox-4.2.2b2 → ipfabric_netbox-4.2.2b4}/ipfabric_netbox/templates/ipfabric_netbox/inc/site_topology_button.html +0 -2
- {ipfabric_netbox-4.2.2b2 → ipfabric_netbox-4.2.2b4}/ipfabric_netbox/templates/ipfabric_netbox/ipfabric_table.html +1 -1
- {ipfabric_netbox-4.2.2b2 → ipfabric_netbox-4.2.2b4}/ipfabric_netbox/templates/ipfabric_netbox/ipfabricsync.html +2 -1
- {ipfabric_netbox-4.2.2b2 → ipfabric_netbox-4.2.2b4}/ipfabric_netbox/templates/ipfabric_netbox/ipfabrictransformmap_restore.html +1 -1
- {ipfabric_netbox-4.2.2b2 → ipfabric_netbox-4.2.2b4}/ipfabric_netbox/tests/api/test_api.py +1 -1
- {ipfabric_netbox-4.2.2b2 → ipfabric_netbox-4.2.2b4}/ipfabric_netbox/tests/test_forms.py +111 -18
- ipfabric_netbox-4.2.2b4/ipfabric_netbox/tests/test_views.py +2166 -0
- {ipfabric_netbox-4.2.2b2 → ipfabric_netbox-4.2.2b4}/ipfabric_netbox/urls.py +15 -14
- {ipfabric_netbox-4.2.2b2 → ipfabric_netbox-4.2.2b4}/ipfabric_netbox/utilities/transform_map.py +12 -10
- {ipfabric_netbox-4.2.2b2 → ipfabric_netbox-4.2.2b4}/ipfabric_netbox/views.py +153 -139
- {ipfabric_netbox-4.2.2b2 → ipfabric_netbox-4.2.2b4}/pyproject.toml +2 -2
- {ipfabric_netbox-4.2.2b2 → ipfabric_netbox-4.2.2b4}/ipfabric_netbox/api/__init__.py +0 -0
- {ipfabric_netbox-4.2.2b2 → ipfabric_netbox-4.2.2b4}/ipfabric_netbox/api/serializers.py +0 -0
- {ipfabric_netbox-4.2.2b2 → ipfabric_netbox-4.2.2b4}/ipfabric_netbox/api/urls.py +0 -0
- {ipfabric_netbox-4.2.2b2 → ipfabric_netbox-4.2.2b4}/ipfabric_netbox/api/views.py +0 -0
- {ipfabric_netbox-4.2.2b2 → ipfabric_netbox-4.2.2b4}/ipfabric_netbox/choices.py +0 -0
- {ipfabric_netbox-4.2.2b2 → ipfabric_netbox-4.2.2b4}/ipfabric_netbox/data/transform_map.json +0 -0
- {ipfabric_netbox-4.2.2b2 → ipfabric_netbox-4.2.2b4}/ipfabric_netbox/exceptions.py +0 -0
- {ipfabric_netbox-4.2.2b2 → ipfabric_netbox-4.2.2b4}/ipfabric_netbox/filtersets.py +0 -0
- {ipfabric_netbox-4.2.2b2 → ipfabric_netbox-4.2.2b4}/ipfabric_netbox/graphql/__init__.py +0 -0
- {ipfabric_netbox-4.2.2b2 → ipfabric_netbox-4.2.2b4}/ipfabric_netbox/graphql/enums.py +0 -0
- {ipfabric_netbox-4.2.2b2 → ipfabric_netbox-4.2.2b4}/ipfabric_netbox/graphql/filters.py +0 -0
- {ipfabric_netbox-4.2.2b2 → ipfabric_netbox-4.2.2b4}/ipfabric_netbox/graphql/schema.py +0 -0
- {ipfabric_netbox-4.2.2b2 → ipfabric_netbox-4.2.2b4}/ipfabric_netbox/graphql/types.py +0 -0
- {ipfabric_netbox-4.2.2b2 → ipfabric_netbox-4.2.2b4}/ipfabric_netbox/jobs.py +0 -0
- {ipfabric_netbox-4.2.2b2 → ipfabric_netbox-4.2.2b4}/ipfabric_netbox/migrations/0001_initial.py +0 -0
- {ipfabric_netbox-4.2.2b2 → ipfabric_netbox-4.2.2b4}/ipfabric_netbox/migrations/0002_ipfabricsnapshot_status.py +0 -0
- {ipfabric_netbox-4.2.2b2 → ipfabric_netbox-4.2.2b4}/ipfabric_netbox/migrations/0003_ipfabricsource_type_and_more.py +0 -0
- {ipfabric_netbox-4.2.2b2 → ipfabric_netbox-4.2.2b4}/ipfabric_netbox/migrations/0004_ipfabricsync_auto_merge.py +0 -0
- {ipfabric_netbox-4.2.2b2 → ipfabric_netbox-4.2.2b4}/ipfabric_netbox/migrations/0005_alter_ipfabricrelationshipfield_source_model_and_more.py +0 -0
- {ipfabric_netbox-4.2.2b2 → ipfabric_netbox-4.2.2b4}/ipfabric_netbox/migrations/0006_alter_ipfabrictransformmap_target_model.py +0 -0
- {ipfabric_netbox-4.2.2b2 → ipfabric_netbox-4.2.2b4}/ipfabric_netbox/migrations/0007_prepare_custom_fields.py +0 -0
- {ipfabric_netbox-4.2.2b2 → ipfabric_netbox-4.2.2b4}/ipfabric_netbox/migrations/0008_prepare_transform_maps.py +0 -0
- {ipfabric_netbox-4.2.2b2 → ipfabric_netbox-4.2.2b4}/ipfabric_netbox/migrations/0009_transformmap_changes_for_netbox_v4_2.py +0 -0
- {ipfabric_netbox-4.2.2b2 → ipfabric_netbox-4.2.2b4}/ipfabric_netbox/migrations/0010_remove_uuid_from_get_or_create.py +0 -0
- {ipfabric_netbox-4.2.2b2 → ipfabric_netbox-4.2.2b4}/ipfabric_netbox/migrations/0011_update_part_number_DCIM_inventory_item_template.py +0 -0
- {ipfabric_netbox-4.2.2b2 → ipfabric_netbox-4.2.2b4}/ipfabric_netbox/migrations/0012_remove_status_field.py +0 -0
- {ipfabric_netbox-4.2.2b2 → ipfabric_netbox-4.2.2b4}/ipfabric_netbox/migrations/0013_switch_to_branching_plugin.py +0 -0
- {ipfabric_netbox-4.2.2b2 → ipfabric_netbox-4.2.2b4}/ipfabric_netbox/migrations/0014_ipfabrictransformmapgroup_ipfabrictransformmap_group.py +0 -0
- {ipfabric_netbox-4.2.2b2 → ipfabric_netbox-4.2.2b4}/ipfabric_netbox/migrations/0015_ipfabricingestionissue.py +0 -0
- {ipfabric_netbox-4.2.2b2 → ipfabric_netbox-4.2.2b4}/ipfabric_netbox/migrations/0016_tags_and_changelog_for_snapshots.py +0 -0
- {ipfabric_netbox-4.2.2b2 → ipfabric_netbox-4.2.2b4}/ipfabric_netbox/migrations/0017_ipfabricsync_update_custom_fields.py +0 -0
- {ipfabric_netbox-4.2.2b2 → ipfabric_netbox-4.2.2b4}/ipfabric_netbox/migrations/0018_remove_type_field.py +0 -0
- {ipfabric_netbox-4.2.2b2 → ipfabric_netbox-4.2.2b4}/ipfabric_netbox/migrations/__init__.py +0 -0
- {ipfabric_netbox-4.2.2b2 → ipfabric_netbox-4.2.2b4}/ipfabric_netbox/navigation.py +0 -0
- {ipfabric_netbox-4.2.2b2 → ipfabric_netbox-4.2.2b4}/ipfabric_netbox/signals.py +0 -0
- {ipfabric_netbox-4.2.2b2 → ipfabric_netbox-4.2.2b4}/ipfabric_netbox/tables.py +0 -0
- {ipfabric_netbox-4.2.2b2 → ipfabric_netbox-4.2.2b4}/ipfabric_netbox/templates/ipfabric_netbox/inc/clone_form.html +0 -0
- {ipfabric_netbox-4.2.2b2 → ipfabric_netbox-4.2.2b4}/ipfabric_netbox/templates/ipfabric_netbox/inc/diff.html +0 -0
- {ipfabric_netbox-4.2.2b2 → ipfabric_netbox-4.2.2b4}/ipfabric_netbox/templates/ipfabric_netbox/inc/json.html +0 -0
- {ipfabric_netbox-4.2.2b2 → ipfabric_netbox-4.2.2b4}/ipfabric_netbox/templates/ipfabric_netbox/inc/logs_pending.html +0 -0
- {ipfabric_netbox-4.2.2b2 → ipfabric_netbox-4.2.2b4}/ipfabric_netbox/templates/ipfabric_netbox/inc/merge_form.html +0 -0
- {ipfabric_netbox-4.2.2b2 → ipfabric_netbox-4.2.2b4}/ipfabric_netbox/templates/ipfabric_netbox/inc/site_topology_modal.html +0 -0
- {ipfabric_netbox-4.2.2b2 → ipfabric_netbox-4.2.2b4}/ipfabric_netbox/templates/ipfabric_netbox/inc/snapshotdata.html +0 -0
- {ipfabric_netbox-4.2.2b2 → ipfabric_netbox-4.2.2b4}/ipfabric_netbox/templates/ipfabric_netbox/inc/transform_map_field_map.html +0 -0
- {ipfabric_netbox-4.2.2b2 → ipfabric_netbox-4.2.2b4}/ipfabric_netbox/templates/ipfabric_netbox/inc/transform_map_relationship_map.html +0 -0
- {ipfabric_netbox-4.2.2b2 → ipfabric_netbox-4.2.2b4}/ipfabric_netbox/templates/ipfabric_netbox/ipfabricingestion.html +0 -0
- {ipfabric_netbox-4.2.2b2 → ipfabric_netbox-4.2.2b4}/ipfabric_netbox/templates/ipfabric_netbox/ipfabricsnapshot.html +0 -0
- {ipfabric_netbox-4.2.2b2 → ipfabric_netbox-4.2.2b4}/ipfabric_netbox/templates/ipfabric_netbox/ipfabricsource.html +0 -0
- {ipfabric_netbox-4.2.2b2 → ipfabric_netbox-4.2.2b4}/ipfabric_netbox/templates/ipfabric_netbox/ipfabrictransformmap.html +0 -0
- {ipfabric_netbox-4.2.2b2 → ipfabric_netbox-4.2.2b4}/ipfabric_netbox/templates/ipfabric_netbox/ipfabrictransformmap_list.html +0 -0
- {ipfabric_netbox-4.2.2b2 → ipfabric_netbox-4.2.2b4}/ipfabric_netbox/templates/ipfabric_netbox/ipfabrictransformmapgroup.html +0 -0
- {ipfabric_netbox-4.2.2b2 → ipfabric_netbox-4.2.2b4}/ipfabric_netbox/templates/ipfabric_netbox/partials/ingestion_all.html +0 -0
- {ipfabric_netbox-4.2.2b2 → ipfabric_netbox-4.2.2b4}/ipfabric_netbox/templates/ipfabric_netbox/partials/ingestion_progress.html +0 -0
- {ipfabric_netbox-4.2.2b2 → ipfabric_netbox-4.2.2b4}/ipfabric_netbox/templates/ipfabric_netbox/partials/ingestion_statistics.html +0 -0
- {ipfabric_netbox-4.2.2b2 → ipfabric_netbox-4.2.2b4}/ipfabric_netbox/templates/ipfabric_netbox/partials/ingestion_status.html +0 -0
- {ipfabric_netbox-4.2.2b2 → ipfabric_netbox-4.2.2b4}/ipfabric_netbox/templates/ipfabric_netbox/partials/job_logs.html +0 -0
- {ipfabric_netbox-4.2.2b2 → ipfabric_netbox-4.2.2b4}/ipfabric_netbox/templates/ipfabric_netbox/partials/object_tabs.html +0 -0
- {ipfabric_netbox-4.2.2b2 → ipfabric_netbox-4.2.2b4}/ipfabric_netbox/templates/ipfabric_netbox/partials/sync_last_ingestion.html +0 -0
- {ipfabric_netbox-4.2.2b2 → ipfabric_netbox-4.2.2b4}/ipfabric_netbox/templates/static/ipfabric_netbox/css/rack.css +0 -0
- {ipfabric_netbox-4.2.2b2 → ipfabric_netbox-4.2.2b4}/ipfabric_netbox/templatetags/__init__.py +0 -0
- {ipfabric_netbox-4.2.2b2 → ipfabric_netbox-4.2.2b4}/ipfabric_netbox/templatetags/ipfabric_netbox_helpers.py +0 -0
- {ipfabric_netbox-4.2.2b2 → ipfabric_netbox-4.2.2b4}/ipfabric_netbox/tests/__init__.py +0 -0
- {ipfabric_netbox-4.2.2b2 → ipfabric_netbox-4.2.2b4}/ipfabric_netbox/tests/api/__init__.py +0 -0
- {ipfabric_netbox-4.2.2b2 → ipfabric_netbox-4.2.2b4}/ipfabric_netbox/tests/test_models.py +0 -0
- {ipfabric_netbox-4.2.2b2 → ipfabric_netbox-4.2.2b4}/ipfabric_netbox/utilities/__init__.py +0 -0
- {ipfabric_netbox-4.2.2b2 → ipfabric_netbox-4.2.2b4}/ipfabric_netbox/utilities/ipfutils.py +0 -0
- {ipfabric_netbox-4.2.2b2 → ipfabric_netbox-4.2.2b4}/ipfabric_netbox/utilities/logging.py +0 -0
- {ipfabric_netbox-4.2.2b2 → ipfabric_netbox-4.2.2b4}/ipfabric_netbox/utilities/nbutils.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ipfabric_netbox
|
|
3
|
-
Version: 4.2.
|
|
3
|
+
Version: 4.2.2b4
|
|
4
4
|
Summary: NetBox plugin to sync IP Fabric data into NetBox
|
|
5
5
|
License: MIT
|
|
6
6
|
Keywords: netbox,ipfabric,plugin,sync
|
|
@@ -24,7 +24,7 @@ Requires-Dist: ipfabric (>=6.6.4) ; extra != "ipfabric_6_10" and extra != "ipfab
|
|
|
24
24
|
Requires-Dist: ipfabric (>=7.0.0,<7.1.0) ; extra != "ipfabric_6_10" and extra == "ipfabric_7_0" and extra != "ipfabric_7_2" and extra != "ipfabric_7_3"
|
|
25
25
|
Requires-Dist: ipfabric (>=7.2.0,<7.3.0) ; extra != "ipfabric_6_10" and extra != "ipfabric_7_0" and extra == "ipfabric_7_2" and extra != "ipfabric_7_3"
|
|
26
26
|
Requires-Dist: ipfabric (>=7.3.0,<7.4.0) ; extra != "ipfabric_6_10" and extra != "ipfabric_7_0" and extra != "ipfabric_7_2" and extra == "ipfabric_7_3"
|
|
27
|
-
Requires-Dist: netboxlabs-netbox-branching (
|
|
27
|
+
Requires-Dist: netboxlabs-netbox-branching (==0.7.0)
|
|
28
28
|
Requires-Dist: netutils
|
|
29
29
|
Project-URL: Bug Tracker, https://gitlab.com/ip-fabric/integrations/ipfabric-netbox-sync/-/issues
|
|
30
30
|
Project-URL: Homepage, https://gitlab.com/ip-fabric/integrations/ipfabric-netbox-sync
|
|
@@ -65,7 +65,9 @@ These are the required NetBox versions for corresponding plugin version. Any oth
|
|
|
65
65
|
|
|
66
66
|
| Netbox Version | Plugin Version |
|
|
67
67
|
|----------------|----------------|
|
|
68
|
-
| 4.
|
|
68
|
+
| 4.4.0 and up | 4.3.0 and up |
|
|
69
|
+
| 4.3.0 - 4.3.7 | 4.2.2 |
|
|
70
|
+
| 4.3.0 - 4.3.6 | 4.0.0 - 4.2.1 |
|
|
69
71
|
| 4.2.4 - 4.2.9 | 3.2.2 - 3.2.4 |
|
|
70
72
|
| 4.2.0 - 4.2.3 | 3.2.0 |
|
|
71
73
|
| 4.1.5 - 4.1.11 | 3.1.1 - 3.1.3 |
|
|
@@ -32,7 +32,9 @@ These are the required NetBox versions for corresponding plugin version. Any oth
|
|
|
32
32
|
|
|
33
33
|
| Netbox Version | Plugin Version |
|
|
34
34
|
|----------------|----------------|
|
|
35
|
-
| 4.
|
|
35
|
+
| 4.4.0 and up | 4.3.0 and up |
|
|
36
|
+
| 4.3.0 - 4.3.7 | 4.2.2 |
|
|
37
|
+
| 4.3.0 - 4.3.6 | 4.0.0 - 4.2.1 |
|
|
36
38
|
| 4.2.4 - 4.2.9 | 3.2.2 - 3.2.4 |
|
|
37
39
|
| 4.2.0 - 4.2.3 | 3.2.0 |
|
|
38
40
|
| 4.1.5 - 4.1.11 | 3.1.1 - 3.1.3 |
|
|
@@ -6,9 +6,9 @@ class NetboxIPFabricConfig(PluginConfig):
|
|
|
6
6
|
name = "ipfabric_netbox"
|
|
7
7
|
verbose_name = "NetBox IP Fabric SoT Plugin"
|
|
8
8
|
description = "Sync IP Fabric into NetBox"
|
|
9
|
-
version = "4.2.
|
|
9
|
+
version = "4.2.2b4"
|
|
10
10
|
base_url = "ipfabric"
|
|
11
|
-
min_version = "4.
|
|
11
|
+
min_version = "4.4.0"
|
|
12
12
|
|
|
13
13
|
def ready(self):
|
|
14
14
|
super().ready()
|
|
@@ -263,13 +263,13 @@ class IPFabricTransformFieldForm(NetBoxModelForm):
|
|
|
263
263
|
)
|
|
264
264
|
|
|
265
265
|
|
|
266
|
-
class IPFabricTransformMapGroupForm(
|
|
266
|
+
class IPFabricTransformMapGroupForm(NetBoxModelForm):
|
|
267
267
|
class Meta:
|
|
268
268
|
model = IPFabricTransformMapGroup
|
|
269
269
|
fields = ("name", "description")
|
|
270
270
|
|
|
271
271
|
|
|
272
|
-
class IPFabricTransformMapForm(
|
|
272
|
+
class IPFabricTransformMapForm(NetBoxModelForm):
|
|
273
273
|
class Meta:
|
|
274
274
|
model = IPFabricTransformMap
|
|
275
275
|
fields = ("name", "group", "source_model", "target_model")
|
|
@@ -531,13 +531,28 @@ class IPFabricSyncForm(NetBoxModelForm):
|
|
|
531
531
|
widgets = {"source": HTMXSelect()}
|
|
532
532
|
|
|
533
533
|
def __init__(self, *args, **kwargs):
|
|
534
|
+
# The initial data for BooleanFields change to list on HTMX requests.
|
|
535
|
+
# This causes URL to bloat, so we need to sanitize it before it's used.
|
|
536
|
+
initial = kwargs.get("initial", {}).copy()
|
|
537
|
+
for name, value in initial.items():
|
|
538
|
+
if (
|
|
539
|
+
(
|
|
540
|
+
name.startswith("ipf_")
|
|
541
|
+
or (
|
|
542
|
+
name in self.base_fields
|
|
543
|
+
and isinstance(self.base_fields[name], forms.BooleanField)
|
|
544
|
+
)
|
|
545
|
+
)
|
|
546
|
+
and isinstance(value, list)
|
|
547
|
+
and len(value) > 1
|
|
548
|
+
):
|
|
549
|
+
initial[name] = value[-1] # Keep only the last value
|
|
550
|
+
kwargs["initial"] = initial
|
|
534
551
|
super().__init__(*args, **kwargs)
|
|
535
|
-
|
|
536
|
-
|
|
552
|
+
|
|
553
|
+
source = get_field_value(self, "source")
|
|
537
554
|
|
|
538
555
|
if not self.data:
|
|
539
|
-
if ingestion_type:
|
|
540
|
-
self.source_type = IPFabricSource.objects.get(pk=ingestion_type).type
|
|
541
556
|
if sites := get_field_value(self, "sites"):
|
|
542
557
|
sites = list_to_choices(str_to_list(sites))
|
|
543
558
|
self.fields["sites"].choices = sites
|
|
@@ -547,46 +562,82 @@ class IPFabricSyncForm(NetBoxModelForm):
|
|
|
547
562
|
snapshot_sites = IPFabricSnapshot.objects.get(pk=snapshot_id).sites
|
|
548
563
|
choices = list_to_choices(str_to_list(snapshot_sites))
|
|
549
564
|
self.fields["sites"].choices = choices
|
|
565
|
+
source = self.data.get("source")
|
|
550
566
|
|
|
567
|
+
# These values are not populated by data on instance, so we need to set them manually
|
|
551
568
|
if self.instance and self.instance.pk:
|
|
552
569
|
if not kwargs.get("initial"):
|
|
553
|
-
|
|
554
|
-
self.initial["source"] = self.instance.snapshot_data.source
|
|
570
|
+
source = self.instance.snapshot_data.source
|
|
555
571
|
if not self.data:
|
|
556
572
|
self.fields["sites"].choices = list_to_choices(
|
|
557
573
|
self.instance.snapshot_data.sites
|
|
558
574
|
)
|
|
559
|
-
|
|
560
|
-
|
|
575
|
+
else:
|
|
576
|
+
source = kwargs["initial"].get(
|
|
577
|
+
"source", self.instance.snapshot_data.source
|
|
578
|
+
)
|
|
579
|
+
self.initial["source"] = source
|
|
580
|
+
if "groups" not in self.initial:
|
|
561
581
|
self.initial["groups"] = self.instance.parameters.get("groups", [])
|
|
562
582
|
|
|
583
|
+
# Handle sites field initialization
|
|
584
|
+
if "sites" not in self.initial:
|
|
585
|
+
selected_sites = self.instance.parameters.get("sites", [])
|
|
586
|
+
self.initial["sites"] = selected_sites
|
|
587
|
+
|
|
588
|
+
# Ensure the field has the selected sites as both choices and initial values
|
|
589
|
+
if not self.data and selected_sites:
|
|
590
|
+
# Get current choices and ensure selected sites are included
|
|
591
|
+
current_choices = (
|
|
592
|
+
list(self.fields["sites"].choices)
|
|
593
|
+
if hasattr(self.fields["sites"], "choices")
|
|
594
|
+
else []
|
|
595
|
+
)
|
|
596
|
+
current_choice_values = [choice[0] for choice in current_choices]
|
|
597
|
+
|
|
598
|
+
# Add any missing selected sites to choices
|
|
599
|
+
for site in str_to_list(selected_sites):
|
|
600
|
+
if site not in current_choice_values:
|
|
601
|
+
current_choices.append((site, site))
|
|
602
|
+
|
|
603
|
+
self.fields["sites"].choices = current_choices
|
|
604
|
+
self.fields["sites"].initial = selected_sites
|
|
605
|
+
else:
|
|
606
|
+
self.fields["sites"].initial = self.initial["sites"]
|
|
607
|
+
|
|
563
608
|
now = local_now().strftime("%Y-%m-%d %H:%M:%S")
|
|
564
609
|
self.fields["scheduled"].help_text += f" (current time: <strong>{now}</strong>)"
|
|
565
610
|
|
|
566
611
|
# Add backend-specific form fields
|
|
567
612
|
self.backend_fields = {}
|
|
568
613
|
|
|
614
|
+
# Prepare buttons for each target Model
|
|
569
615
|
for k, v in sync_parameters.items():
|
|
570
616
|
self.backend_fields[k] = []
|
|
571
617
|
for name, form_field in v.items():
|
|
572
618
|
field_name = f"ipf_{name}"
|
|
573
619
|
self.backend_fields[k].append(field_name)
|
|
574
|
-
self.fields[field_name] = copy.
|
|
620
|
+
self.fields[field_name] = copy.deepcopy(form_field)
|
|
575
621
|
if self.instance and self.instance.parameters:
|
|
576
|
-
|
|
622
|
+
value = self.instance.parameters.get(name)
|
|
623
|
+
self.fields[field_name].initial = value
|
|
577
624
|
|
|
578
625
|
# Set fieldsets dynamically based and backend_fields
|
|
579
626
|
fieldsets = [
|
|
580
627
|
FieldSet("name", "source", "groups", name=_("IP Fabric Source")),
|
|
581
628
|
]
|
|
582
|
-
if
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
629
|
+
# Only show snapshot and sites if source is selected
|
|
630
|
+
if source:
|
|
631
|
+
if isinstance(source, str) or isinstance(source, int):
|
|
632
|
+
source = IPFabricSource.objects.get(pk=source)
|
|
633
|
+
if source.type == "local":
|
|
634
|
+
fieldsets.append(
|
|
635
|
+
FieldSet("snapshot_data", "sites", name=_("Snapshot Information")),
|
|
636
|
+
)
|
|
637
|
+
else:
|
|
638
|
+
fieldsets.append(
|
|
639
|
+
FieldSet("snapshot_data", name=_("Snapshot Information")),
|
|
640
|
+
)
|
|
590
641
|
for k, v in self.backend_fields.items():
|
|
591
642
|
fieldsets.append(FieldSet(*v, name=f"{k.upper()} Parameters"))
|
|
592
643
|
fieldsets.append(
|
|
@@ -602,10 +653,22 @@ class IPFabricSyncForm(NetBoxModelForm):
|
|
|
602
653
|
def clean(self):
|
|
603
654
|
super().clean()
|
|
604
655
|
|
|
656
|
+
source = self.cleaned_data.get("source")
|
|
657
|
+
snapshot = self.cleaned_data.get("snapshot_data")
|
|
658
|
+
|
|
659
|
+
if (
|
|
660
|
+
source
|
|
661
|
+
and snapshot
|
|
662
|
+
and IPFabricSource.objects.get(pk=source.pk)
|
|
663
|
+
!= IPFabricSnapshot.objects.get(pk=snapshot.pk).source
|
|
664
|
+
):
|
|
665
|
+
raise ValidationError(
|
|
666
|
+
{"snapshot_data": _("Snapshot does not belong to the selected source.")}
|
|
667
|
+
)
|
|
668
|
+
|
|
605
669
|
sites = self.data.get("sites")
|
|
606
670
|
self.fields["sites"].choices = list_to_choices(str_to_list(sites))
|
|
607
671
|
if sites and "snapshot_data" in self.cleaned_data:
|
|
608
|
-
snapshot = self.cleaned_data["snapshot_data"]
|
|
609
672
|
# Check if all sites are valid - fail if any site is not found in snapshot.sites
|
|
610
673
|
if not all(
|
|
611
674
|
any(site in snapshot_site for snapshot_site in snapshot.sites)
|
|
@@ -29,6 +29,7 @@ if TYPE_CHECKING:
|
|
|
29
29
|
|
|
30
30
|
def create_custom_field(
|
|
31
31
|
apps: "apps_type",
|
|
32
|
+
db_alias: str,
|
|
32
33
|
field_name: str,
|
|
33
34
|
label: str,
|
|
34
35
|
target_models: list,
|
|
@@ -37,6 +38,7 @@ def create_custom_field(
|
|
|
37
38
|
):
|
|
38
39
|
"""Create a single custom field and link it to required models."""
|
|
39
40
|
ObjectType = apps.get_model("core", "ObjectType")
|
|
41
|
+
ContentType = apps.get_model("contenttypes", "ContentType")
|
|
40
42
|
|
|
41
43
|
defaults = {
|
|
42
44
|
"label": label,
|
|
@@ -47,23 +49,29 @@ def create_custom_field(
|
|
|
47
49
|
"ui_editable": getattr(CustomFieldUIEditableChoices, "NO"),
|
|
48
50
|
}
|
|
49
51
|
|
|
50
|
-
custom_field, _ =
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
52
|
+
custom_field, _ = (
|
|
53
|
+
apps.get_model("extras", "CustomField")
|
|
54
|
+
.objects.using(db_alias)
|
|
55
|
+
.update_or_create(
|
|
56
|
+
type=getattr(CustomFieldTypeChoices, cf_type.upper()),
|
|
57
|
+
name=field_name,
|
|
58
|
+
defaults=defaults,
|
|
59
|
+
)
|
|
54
60
|
)
|
|
55
61
|
|
|
56
62
|
for model in target_models:
|
|
57
|
-
custom_field.object_types.add(
|
|
63
|
+
custom_field.object_types.add(ContentType.objects.get_for_model(model))
|
|
58
64
|
|
|
59
65
|
|
|
60
66
|
def prepare_custom_fields(apps: "apps_type", schema_editor: "BaseDatabaseSchemaEditor"):
|
|
61
67
|
"""Forward migration to prepare ipfabric_netbox custom fields and links."""
|
|
68
|
+
db_alias = schema_editor.connection.alias
|
|
62
69
|
Device = apps.get_model("dcim", "Device")
|
|
63
70
|
Site = apps.get_model("dcim", "Site")
|
|
64
71
|
|
|
65
72
|
create_custom_field(
|
|
66
73
|
apps,
|
|
74
|
+
db_alias,
|
|
67
75
|
"ipfabric_source",
|
|
68
76
|
"IP Fabric Source",
|
|
69
77
|
[Device, Site],
|
|
@@ -72,31 +80,41 @@ def prepare_custom_fields(apps: "apps_type", schema_editor: "BaseDatabaseSchemaE
|
|
|
72
80
|
)
|
|
73
81
|
create_custom_field(
|
|
74
82
|
apps,
|
|
83
|
+
db_alias,
|
|
75
84
|
"ipfabric_ingestion",
|
|
76
85
|
"IP Fabric Last Ingestion",
|
|
77
86
|
[Device, Site],
|
|
78
87
|
cf_type="type_object",
|
|
79
88
|
object_type=apps.get_model("ipfabric_netbox", "IPFabricIngestion"),
|
|
80
89
|
)
|
|
81
|
-
cl, _ =
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
90
|
+
cl, _ = (
|
|
91
|
+
apps.get_model("extras", "CustomLink")
|
|
92
|
+
.objects.using(db_alias)
|
|
93
|
+
.update_or_create(
|
|
94
|
+
defaults={
|
|
95
|
+
"link_text": "{% if object.custom_field_data.ipfabric_source is defined %}{% set SOURCE_ID = object.custom_field_data.ipfabric_source %}{% if SOURCE_ID %}IP Fabric{% endif %}{% endif %}",
|
|
96
|
+
"link_url": '{% if object.custom_field_data.ipfabric_source is defined %}{% set SOURCE_ID = object.custom_field_data.ipfabric_source %}{% if SOURCE_ID %}{% set BASE_URL = object.custom_fields.filter(related_object_type__model="ipfabricsource").first().related_object_type.model_class().objects.get(pk=SOURCE_ID).url %}{{ BASE_URL }}/inventory/devices?options={"filters":{"sn": ["like","{{ object.serial }}"]}}{% endif %}{%endif%}',
|
|
97
|
+
"new_window": True,
|
|
98
|
+
"button_class": CustomLinkButtonClassChoices.BLUE,
|
|
99
|
+
},
|
|
100
|
+
name="ipfabric",
|
|
101
|
+
)
|
|
89
102
|
)
|
|
90
103
|
cl.object_types.add(
|
|
91
|
-
apps.get_model("
|
|
104
|
+
apps.get_model("contenttypes", "ContentType")
|
|
105
|
+
.objects.db_manager(db_alias)
|
|
106
|
+
.get_for_model(Device)
|
|
92
107
|
)
|
|
93
108
|
|
|
94
109
|
|
|
95
110
|
def cleanup_custom_fields(apps: "apps_type", schema_editor: "BaseDatabaseSchemaEditor"):
|
|
96
111
|
"""Reverse migration to prepare ipfabric_netbox custom fields and links."""
|
|
112
|
+
db_alias = schema_editor.connection.alias
|
|
97
113
|
for field_name in ["ipfabric_source", "ipfabric_ingestion"]:
|
|
98
|
-
custom_field =
|
|
99
|
-
|
|
114
|
+
custom_field = (
|
|
115
|
+
apps.get_model("extras", "CustomField")
|
|
116
|
+
.objects.using(db_alias)
|
|
117
|
+
.get(name=field_name)
|
|
100
118
|
)
|
|
101
119
|
for model in custom_field.object_types.all()[:]:
|
|
102
120
|
custom_field.object_types.remove(model)
|
|
@@ -109,7 +127,9 @@ def prepare_transform_maps(
|
|
|
109
127
|
"""Create transform maps if they do not exist yet.
|
|
110
128
|
They used to be created during plugin.ready() so they might be present on older DBs.
|
|
111
129
|
"""
|
|
112
|
-
build_transform_maps(
|
|
130
|
+
build_transform_maps(
|
|
131
|
+
data=get_transform_map(), apps=apps, db_alias=schema_editor.connection.alias
|
|
132
|
+
)
|
|
113
133
|
|
|
114
134
|
|
|
115
135
|
class Migration(migrations.Migration):
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# Generated by Django 5.2.5 on 2025-09-22 07:43
|
|
2
|
+
from django.db import migrations
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class Migration(migrations.Migration):
|
|
6
|
+
dependencies = [
|
|
7
|
+
("ipfabric_netbox", "0018_remove_type_field"),
|
|
8
|
+
]
|
|
9
|
+
|
|
10
|
+
operations = [
|
|
11
|
+
migrations.AlterModelOptions(
|
|
12
|
+
name="ipfabrictransformmap",
|
|
13
|
+
options={"ordering": ("pk",)},
|
|
14
|
+
),
|
|
15
|
+
migrations.AlterModelOptions(
|
|
16
|
+
name="ipfabrictransformmapgroup",
|
|
17
|
+
options={"ordering": ("pk",)},
|
|
18
|
+
),
|
|
19
|
+
]
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import ast
|
|
2
|
+
import functools
|
|
2
3
|
import json
|
|
3
4
|
import logging
|
|
4
5
|
import traceback
|
|
@@ -12,6 +13,7 @@ from core.models import Job
|
|
|
12
13
|
from core.models import ObjectType
|
|
13
14
|
from core.signals import pre_sync
|
|
14
15
|
from dcim.models import Device
|
|
16
|
+
from dcim.models import Site
|
|
15
17
|
from dcim.models import VirtualChassis
|
|
16
18
|
from dcim.signals import assign_virtualchassis_master
|
|
17
19
|
from django.apps import apps
|
|
@@ -34,10 +36,10 @@ from netbox.models import NetBoxModel
|
|
|
34
36
|
from netbox.models import PrimaryModel
|
|
35
37
|
from netbox.models.features import JobsMixin
|
|
36
38
|
from netbox.models.features import TagsMixin
|
|
37
|
-
from netbox.registry import registry
|
|
38
39
|
from netbox_branching.choices import BranchStatusChoices
|
|
39
40
|
from netbox_branching.contextvars import active_branch
|
|
40
41
|
from netbox_branching.models import Branch
|
|
42
|
+
from netbox_branching.utilities import supports_branching
|
|
41
43
|
from utilities.querysets import RestrictedQuerySet
|
|
42
44
|
from utilities.request import NetBoxFakeRequest
|
|
43
45
|
|
|
@@ -100,6 +102,7 @@ class IPFabricTransformMapGroup(NetBoxModel):
|
|
|
100
102
|
description = models.TextField(blank=True, null=True)
|
|
101
103
|
|
|
102
104
|
class Meta:
|
|
105
|
+
ordering = ("pk",)
|
|
103
106
|
verbose_name = "IP Fabric Transform Map Group"
|
|
104
107
|
verbose_name_plural = "IP Fabric Transform Map Groups"
|
|
105
108
|
|
|
@@ -136,6 +139,7 @@ class IPFabricTransformMap(NetBoxModel):
|
|
|
136
139
|
)
|
|
137
140
|
|
|
138
141
|
class Meta:
|
|
142
|
+
ordering = ("pk",)
|
|
139
143
|
verbose_name = "IP Fabric Transform Map"
|
|
140
144
|
verbose_name_plural = "IP Fabric Transform Maps"
|
|
141
145
|
|
|
@@ -173,13 +177,17 @@ class IPFabricTransformMap(NetBoxModel):
|
|
|
173
177
|
)
|
|
174
178
|
return cleaned_data
|
|
175
179
|
|
|
180
|
+
@functools.cache
|
|
176
181
|
def get_models(self):
|
|
177
182
|
_context = dict()
|
|
178
183
|
|
|
179
|
-
for app,
|
|
184
|
+
for app, app_models in apps.all_models.items():
|
|
180
185
|
_context.setdefault(app, {})
|
|
181
|
-
for
|
|
182
|
-
|
|
186
|
+
for model in app_models:
|
|
187
|
+
if isinstance(model, str):
|
|
188
|
+
model = apps.get_registered_model(app, model)
|
|
189
|
+
if not supports_branching(model):
|
|
190
|
+
continue
|
|
183
191
|
_context[app][model.__name__] = model
|
|
184
192
|
_context["contenttypes"] = {}
|
|
185
193
|
_context["contenttypes"]["ContentType"] = ContentType
|
|
@@ -246,10 +254,16 @@ class IPFabricTransformMap(NetBoxModel):
|
|
|
246
254
|
# See NetBox docs Customization -> Custom Scripts -> Change Logging
|
|
247
255
|
instance = queryset.get(**context)
|
|
248
256
|
instance.snapshot()
|
|
257
|
+
changed = False
|
|
249
258
|
for attr, value in defaults.items():
|
|
259
|
+
# Only run data validation and save if something has changed
|
|
260
|
+
if getattr(instance, attr) == value:
|
|
261
|
+
continue
|
|
262
|
+
changed = True
|
|
250
263
|
setattr(instance, attr, value)
|
|
251
|
-
|
|
252
|
-
|
|
264
|
+
if changed:
|
|
265
|
+
instance.full_clean()
|
|
266
|
+
instance.save(using=connection_name)
|
|
253
267
|
except target_class.DoesNotExist:
|
|
254
268
|
for field in list(context.keys()):
|
|
255
269
|
# When assigning we need to replace `field__isnull=True` with `field=None`
|
|
@@ -543,6 +557,13 @@ class IPFabricSource(IPFabricClient, JobsMixin, PrimaryModel):
|
|
|
543
557
|
# Emit the post_sync signal
|
|
544
558
|
# post_sync.send(sender=self.__class__, instance=self)
|
|
545
559
|
|
|
560
|
+
@classmethod
|
|
561
|
+
def get_for_site(cls, site: Site):
|
|
562
|
+
"""Get all snapshots containing the given site."""
|
|
563
|
+
return cls.objects.filter(
|
|
564
|
+
Q(snapshots__data__sites__contains=[site.name])
|
|
565
|
+
).distinct()
|
|
566
|
+
|
|
546
567
|
|
|
547
568
|
class IPFabricSnapshot(TagsMixin, ChangeLoggedModel):
|
|
548
569
|
source = models.ForeignKey(
|
|
@@ -1004,3 +1025,6 @@ class IPFabricData(models.Model):
|
|
|
1004
1025
|
choices=IPFabricRawDataTypeChoices,
|
|
1005
1026
|
)
|
|
1006
1027
|
objects = RestrictedQuerySet.as_manager()
|
|
1028
|
+
|
|
1029
|
+
def get_absolute_url(self):
|
|
1030
|
+
return reverse("plugins:ipfabric_netbox:ipfabricdata_data", args=[self.pk])
|
|
@@ -1,23 +1,26 @@
|
|
|
1
1
|
import logging
|
|
2
2
|
|
|
3
|
+
from dcim.models import Site
|
|
3
4
|
from netbox.plugins import PluginTemplateExtension
|
|
4
5
|
|
|
5
|
-
from ipfabric_netbox.models import
|
|
6
|
+
from ipfabric_netbox.models import IPFabricSource
|
|
6
7
|
|
|
7
8
|
logger = logging.getLogger("ipfabric_netbox.template_content")
|
|
8
9
|
|
|
9
10
|
|
|
10
11
|
class SiteTopologyButtons(PluginTemplateExtension):
|
|
11
|
-
|
|
12
|
+
models = ["dcim.site"]
|
|
12
13
|
|
|
13
14
|
def buttons(self):
|
|
14
15
|
try:
|
|
15
16
|
site = self.context.get("object")
|
|
16
17
|
source = None
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
18
|
+
if isinstance(site, Site) and (
|
|
19
|
+
source_id := site.custom_field_data.get("ipfabric_source")
|
|
20
|
+
):
|
|
21
|
+
source = IPFabricSource.objects.filter(id=source_id).first()
|
|
22
|
+
# `source_id` saved in CF might be obsolete, so always fall back to search by site
|
|
23
|
+
source = source or IPFabricSource.get_for_site(site).first()
|
|
21
24
|
return self.render(
|
|
22
25
|
"ipfabric_netbox/inc/site_topology_button.html",
|
|
23
26
|
extra_context={"source": source},
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
{% if object.custom_field_data.ipfabric_source %}
|
|
2
1
|
<div class="btn-group" role="group" aria-label="Button group with nested dropdown">
|
|
3
2
|
<div class="btn-group" role="group">
|
|
4
3
|
<button id="btnGroupDrop1" type="button" class="btn btn-sm btn-success dropdown-toggle" data-bs-toggle="dropdown"
|
|
@@ -48,7 +47,6 @@
|
|
|
48
47
|
</div>
|
|
49
48
|
</div>
|
|
50
49
|
</div>
|
|
51
|
-
{% endif %}
|
|
52
50
|
|
|
53
51
|
{% block javascript %}
|
|
54
52
|
<script>
|
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
<div class="row px-3">
|
|
48
48
|
<div class="card">
|
|
49
49
|
<div class="card-body">
|
|
50
|
-
|
|
50
|
+
Could not find an IP Fabric Source that could have synced this Device. The source is used to match devices between NetBox and IP Fabric instance.
|
|
51
51
|
</div>
|
|
52
52
|
</div>
|
|
53
53
|
</div>
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
{% load helpers %}
|
|
4
4
|
{% load plugins %}
|
|
5
5
|
{% load render_table from django_tables2 %}
|
|
6
|
+
{% load perms %}
|
|
6
7
|
{% load ipfabric_netbox_helpers %}
|
|
7
8
|
|
|
8
9
|
{% block extra_controls %}
|
|
@@ -22,7 +23,7 @@
|
|
|
22
23
|
</span>
|
|
23
24
|
{% endif %}
|
|
24
25
|
{% endif %}
|
|
25
|
-
{% endblock %}
|
|
26
|
+
{% endblock extra_controls %}
|
|
26
27
|
|
|
27
28
|
{% block content %}
|
|
28
29
|
<div class="row mb-3">
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
<div class="modal-body">
|
|
10
10
|
<p>
|
|
11
11
|
{% blocktrans trimmed %}
|
|
12
|
-
Are you sure you want to <strong class="text-danger">restore</strong> the Transform Maps? This restores
|
|
12
|
+
Are you sure you want to <strong class="text-danger">restore</strong> the Transform Maps? This restores will overwrite any changes made in NetBox to Transform Maps without a group.
|
|
13
13
|
{% endblocktrans %}
|
|
14
14
|
</p>
|
|
15
15
|
{% if dependent_objects %}
|
|
@@ -620,7 +620,7 @@ class IPFabricSyncTest(APIViewTestCases.APIViewTestCase):
|
|
|
620
620
|
name="Sync Test E",
|
|
621
621
|
snapshot_data=snapshots[1],
|
|
622
622
|
parameters={"device": True, "interface": True},
|
|
623
|
-
auto_merge=
|
|
623
|
+
auto_merge=False,
|
|
624
624
|
)
|
|
625
625
|
IPFabricSync.objects.create(
|
|
626
626
|
name="Sync Test F",
|