ipfabric_netbox 4.2.0b8__tar.gz → 4.2.0b9__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.

Files changed (85) hide show
  1. {ipfabric_netbox-4.2.0b8 → ipfabric_netbox-4.2.0b9}/PKG-INFO +1 -1
  2. {ipfabric_netbox-4.2.0b8 → ipfabric_netbox-4.2.0b9}/ipfabric_netbox/__init__.py +1 -1
  3. {ipfabric_netbox-4.2.0b8 → ipfabric_netbox-4.2.0b9}/ipfabric_netbox/forms.py +83 -131
  4. ipfabric_netbox-4.2.0b9/ipfabric_netbox/tests/test_forms.py +1440 -0
  5. {ipfabric_netbox-4.2.0b8 → ipfabric_netbox-4.2.0b9}/pyproject.toml +1 -1
  6. {ipfabric_netbox-4.2.0b8 → ipfabric_netbox-4.2.0b9}/README.md +0 -0
  7. {ipfabric_netbox-4.2.0b8 → ipfabric_netbox-4.2.0b9}/ipfabric_netbox/api/__init__.py +0 -0
  8. {ipfabric_netbox-4.2.0b8 → ipfabric_netbox-4.2.0b9}/ipfabric_netbox/api/serializers.py +0 -0
  9. {ipfabric_netbox-4.2.0b8 → ipfabric_netbox-4.2.0b9}/ipfabric_netbox/api/urls.py +0 -0
  10. {ipfabric_netbox-4.2.0b8 → ipfabric_netbox-4.2.0b9}/ipfabric_netbox/api/views.py +0 -0
  11. {ipfabric_netbox-4.2.0b8 → ipfabric_netbox-4.2.0b9}/ipfabric_netbox/choices.py +0 -0
  12. {ipfabric_netbox-4.2.0b8 → ipfabric_netbox-4.2.0b9}/ipfabric_netbox/data/transform_map.json +0 -0
  13. {ipfabric_netbox-4.2.0b8 → ipfabric_netbox-4.2.0b9}/ipfabric_netbox/exceptions.py +0 -0
  14. {ipfabric_netbox-4.2.0b8 → ipfabric_netbox-4.2.0b9}/ipfabric_netbox/filtersets.py +0 -0
  15. {ipfabric_netbox-4.2.0b8 → ipfabric_netbox-4.2.0b9}/ipfabric_netbox/graphql/__init__.py +0 -0
  16. {ipfabric_netbox-4.2.0b8 → ipfabric_netbox-4.2.0b9}/ipfabric_netbox/graphql/enums.py +0 -0
  17. {ipfabric_netbox-4.2.0b8 → ipfabric_netbox-4.2.0b9}/ipfabric_netbox/graphql/filters.py +0 -0
  18. {ipfabric_netbox-4.2.0b8 → ipfabric_netbox-4.2.0b9}/ipfabric_netbox/graphql/schema.py +0 -0
  19. {ipfabric_netbox-4.2.0b8 → ipfabric_netbox-4.2.0b9}/ipfabric_netbox/graphql/types.py +0 -0
  20. {ipfabric_netbox-4.2.0b8 → ipfabric_netbox-4.2.0b9}/ipfabric_netbox/jobs.py +0 -0
  21. {ipfabric_netbox-4.2.0b8 → ipfabric_netbox-4.2.0b9}/ipfabric_netbox/migrations/0001_initial.py +0 -0
  22. {ipfabric_netbox-4.2.0b8 → ipfabric_netbox-4.2.0b9}/ipfabric_netbox/migrations/0001_initial_squashed_0013_switch_to_branching_plugin.py +0 -0
  23. {ipfabric_netbox-4.2.0b8 → ipfabric_netbox-4.2.0b9}/ipfabric_netbox/migrations/0002_ipfabricsnapshot_status.py +0 -0
  24. {ipfabric_netbox-4.2.0b8 → ipfabric_netbox-4.2.0b9}/ipfabric_netbox/migrations/0003_ipfabricsource_type_and_more.py +0 -0
  25. {ipfabric_netbox-4.2.0b8 → ipfabric_netbox-4.2.0b9}/ipfabric_netbox/migrations/0004_ipfabricsync_auto_merge.py +0 -0
  26. {ipfabric_netbox-4.2.0b8 → ipfabric_netbox-4.2.0b9}/ipfabric_netbox/migrations/0005_alter_ipfabricrelationshipfield_source_model_and_more.py +0 -0
  27. {ipfabric_netbox-4.2.0b8 → ipfabric_netbox-4.2.0b9}/ipfabric_netbox/migrations/0006_alter_ipfabrictransformmap_target_model.py +0 -0
  28. {ipfabric_netbox-4.2.0b8 → ipfabric_netbox-4.2.0b9}/ipfabric_netbox/migrations/0007_prepare_custom_fields.py +0 -0
  29. {ipfabric_netbox-4.2.0b8 → ipfabric_netbox-4.2.0b9}/ipfabric_netbox/migrations/0008_prepare_transform_maps.py +0 -0
  30. {ipfabric_netbox-4.2.0b8 → ipfabric_netbox-4.2.0b9}/ipfabric_netbox/migrations/0009_transformmap_changes_for_netbox_v4_2.py +0 -0
  31. {ipfabric_netbox-4.2.0b8 → ipfabric_netbox-4.2.0b9}/ipfabric_netbox/migrations/0010_remove_uuid_from_get_or_create.py +0 -0
  32. {ipfabric_netbox-4.2.0b8 → ipfabric_netbox-4.2.0b9}/ipfabric_netbox/migrations/0011_update_part_number_DCIM_inventory_item_template.py +0 -0
  33. {ipfabric_netbox-4.2.0b8 → ipfabric_netbox-4.2.0b9}/ipfabric_netbox/migrations/0012_remove_status_field.py +0 -0
  34. {ipfabric_netbox-4.2.0b8 → ipfabric_netbox-4.2.0b9}/ipfabric_netbox/migrations/0013_switch_to_branching_plugin.py +0 -0
  35. {ipfabric_netbox-4.2.0b8 → ipfabric_netbox-4.2.0b9}/ipfabric_netbox/migrations/0014_ipfabrictransformmapgroup_ipfabrictransformmap_group.py +0 -0
  36. {ipfabric_netbox-4.2.0b8 → ipfabric_netbox-4.2.0b9}/ipfabric_netbox/migrations/0015_ipfabricingestionissue.py +0 -0
  37. {ipfabric_netbox-4.2.0b8 → ipfabric_netbox-4.2.0b9}/ipfabric_netbox/migrations/0016_tags_and_changelog_for_snapshots.py +0 -0
  38. {ipfabric_netbox-4.2.0b8 → ipfabric_netbox-4.2.0b9}/ipfabric_netbox/migrations/0017_ipfabricsync_update_custom_fields.py +0 -0
  39. {ipfabric_netbox-4.2.0b8 → ipfabric_netbox-4.2.0b9}/ipfabric_netbox/migrations/0018_remove_type_field.py +0 -0
  40. {ipfabric_netbox-4.2.0b8 → ipfabric_netbox-4.2.0b9}/ipfabric_netbox/migrations/__init__.py +0 -0
  41. {ipfabric_netbox-4.2.0b8 → ipfabric_netbox-4.2.0b9}/ipfabric_netbox/models.py +0 -0
  42. {ipfabric_netbox-4.2.0b8 → ipfabric_netbox-4.2.0b9}/ipfabric_netbox/navigation.py +0 -0
  43. {ipfabric_netbox-4.2.0b8 → ipfabric_netbox-4.2.0b9}/ipfabric_netbox/signals.py +0 -0
  44. {ipfabric_netbox-4.2.0b8 → ipfabric_netbox-4.2.0b9}/ipfabric_netbox/tables.py +0 -0
  45. {ipfabric_netbox-4.2.0b8 → ipfabric_netbox-4.2.0b9}/ipfabric_netbox/template_content.py +0 -0
  46. {ipfabric_netbox-4.2.0b8 → ipfabric_netbox-4.2.0b9}/ipfabric_netbox/templates/ipfabric_netbox/inc/clone_form.html +0 -0
  47. {ipfabric_netbox-4.2.0b8 → ipfabric_netbox-4.2.0b9}/ipfabric_netbox/templates/ipfabric_netbox/inc/diff.html +0 -0
  48. {ipfabric_netbox-4.2.0b8 → ipfabric_netbox-4.2.0b9}/ipfabric_netbox/templates/ipfabric_netbox/inc/json.html +0 -0
  49. {ipfabric_netbox-4.2.0b8 → ipfabric_netbox-4.2.0b9}/ipfabric_netbox/templates/ipfabric_netbox/inc/logs_pending.html +0 -0
  50. {ipfabric_netbox-4.2.0b8 → ipfabric_netbox-4.2.0b9}/ipfabric_netbox/templates/ipfabric_netbox/inc/merge_form.html +0 -0
  51. {ipfabric_netbox-4.2.0b8 → ipfabric_netbox-4.2.0b9}/ipfabric_netbox/templates/ipfabric_netbox/inc/site_topology_button.html +0 -0
  52. {ipfabric_netbox-4.2.0b8 → ipfabric_netbox-4.2.0b9}/ipfabric_netbox/templates/ipfabric_netbox/inc/site_topology_modal.html +0 -0
  53. {ipfabric_netbox-4.2.0b8 → ipfabric_netbox-4.2.0b9}/ipfabric_netbox/templates/ipfabric_netbox/inc/snapshotdata.html +0 -0
  54. {ipfabric_netbox-4.2.0b8 → ipfabric_netbox-4.2.0b9}/ipfabric_netbox/templates/ipfabric_netbox/inc/transform_map_field_map.html +0 -0
  55. {ipfabric_netbox-4.2.0b8 → ipfabric_netbox-4.2.0b9}/ipfabric_netbox/templates/ipfabric_netbox/inc/transform_map_relationship_map.html +0 -0
  56. {ipfabric_netbox-4.2.0b8 → ipfabric_netbox-4.2.0b9}/ipfabric_netbox/templates/ipfabric_netbox/ipfabric_table.html +0 -0
  57. {ipfabric_netbox-4.2.0b8 → ipfabric_netbox-4.2.0b9}/ipfabric_netbox/templates/ipfabric_netbox/ipfabricingestion.html +0 -0
  58. {ipfabric_netbox-4.2.0b8 → ipfabric_netbox-4.2.0b9}/ipfabric_netbox/templates/ipfabric_netbox/ipfabricsnapshot.html +0 -0
  59. {ipfabric_netbox-4.2.0b8 → ipfabric_netbox-4.2.0b9}/ipfabric_netbox/templates/ipfabric_netbox/ipfabricsource.html +0 -0
  60. {ipfabric_netbox-4.2.0b8 → ipfabric_netbox-4.2.0b9}/ipfabric_netbox/templates/ipfabric_netbox/ipfabricsync.html +0 -0
  61. {ipfabric_netbox-4.2.0b8 → ipfabric_netbox-4.2.0b9}/ipfabric_netbox/templates/ipfabric_netbox/ipfabrictransformmap.html +0 -0
  62. {ipfabric_netbox-4.2.0b8 → ipfabric_netbox-4.2.0b9}/ipfabric_netbox/templates/ipfabric_netbox/ipfabrictransformmap_list.html +0 -0
  63. {ipfabric_netbox-4.2.0b8 → ipfabric_netbox-4.2.0b9}/ipfabric_netbox/templates/ipfabric_netbox/ipfabrictransformmap_restore.html +0 -0
  64. {ipfabric_netbox-4.2.0b8 → ipfabric_netbox-4.2.0b9}/ipfabric_netbox/templates/ipfabric_netbox/ipfabrictransformmapgroup.html +0 -0
  65. {ipfabric_netbox-4.2.0b8 → ipfabric_netbox-4.2.0b9}/ipfabric_netbox/templates/ipfabric_netbox/partials/ingestion_all.html +0 -0
  66. {ipfabric_netbox-4.2.0b8 → ipfabric_netbox-4.2.0b9}/ipfabric_netbox/templates/ipfabric_netbox/partials/ingestion_progress.html +0 -0
  67. {ipfabric_netbox-4.2.0b8 → ipfabric_netbox-4.2.0b9}/ipfabric_netbox/templates/ipfabric_netbox/partials/ingestion_statistics.html +0 -0
  68. {ipfabric_netbox-4.2.0b8 → ipfabric_netbox-4.2.0b9}/ipfabric_netbox/templates/ipfabric_netbox/partials/ingestion_status.html +0 -0
  69. {ipfabric_netbox-4.2.0b8 → ipfabric_netbox-4.2.0b9}/ipfabric_netbox/templates/ipfabric_netbox/partials/job_logs.html +0 -0
  70. {ipfabric_netbox-4.2.0b8 → ipfabric_netbox-4.2.0b9}/ipfabric_netbox/templates/ipfabric_netbox/partials/object_tabs.html +0 -0
  71. {ipfabric_netbox-4.2.0b8 → ipfabric_netbox-4.2.0b9}/ipfabric_netbox/templates/ipfabric_netbox/partials/sync_last_ingestion.html +0 -0
  72. {ipfabric_netbox-4.2.0b8 → ipfabric_netbox-4.2.0b9}/ipfabric_netbox/templates/static/ipfabric_netbox/css/rack.css +0 -0
  73. {ipfabric_netbox-4.2.0b8 → ipfabric_netbox-4.2.0b9}/ipfabric_netbox/templatetags/__init__.py +0 -0
  74. {ipfabric_netbox-4.2.0b8 → ipfabric_netbox-4.2.0b9}/ipfabric_netbox/templatetags/ipfabric_netbox_helpers.py +0 -0
  75. {ipfabric_netbox-4.2.0b8 → ipfabric_netbox-4.2.0b9}/ipfabric_netbox/tests/__init__.py +0 -0
  76. {ipfabric_netbox-4.2.0b8 → ipfabric_netbox-4.2.0b9}/ipfabric_netbox/tests/api/__init__.py +0 -0
  77. {ipfabric_netbox-4.2.0b8 → ipfabric_netbox-4.2.0b9}/ipfabric_netbox/tests/api/test_api.py +0 -0
  78. {ipfabric_netbox-4.2.0b8 → ipfabric_netbox-4.2.0b9}/ipfabric_netbox/tests/test_models.py +0 -0
  79. {ipfabric_netbox-4.2.0b8 → ipfabric_netbox-4.2.0b9}/ipfabric_netbox/urls.py +0 -0
  80. {ipfabric_netbox-4.2.0b8 → ipfabric_netbox-4.2.0b9}/ipfabric_netbox/utilities/__init__.py +0 -0
  81. {ipfabric_netbox-4.2.0b8 → ipfabric_netbox-4.2.0b9}/ipfabric_netbox/utilities/ipfutils.py +0 -0
  82. {ipfabric_netbox-4.2.0b8 → ipfabric_netbox-4.2.0b9}/ipfabric_netbox/utilities/logging.py +0 -0
  83. {ipfabric_netbox-4.2.0b8 → ipfabric_netbox-4.2.0b9}/ipfabric_netbox/utilities/nbutils.py +0 -0
  84. {ipfabric_netbox-4.2.0b8 → ipfabric_netbox-4.2.0b9}/ipfabric_netbox/utilities/transform_map.py +0 -0
  85. {ipfabric_netbox-4.2.0b8 → ipfabric_netbox-4.2.0b9}/ipfabric_netbox/views.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: ipfabric_netbox
3
- Version: 4.2.0b8
3
+ Version: 4.2.0b9
4
4
  Summary: NetBox plugin to sync IP Fabric data into NetBox
5
5
  License: MIT
6
6
  Keywords: netbox,ipfabric,plugin,sync
@@ -6,7 +6,7 @@ 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.0b8"
9
+ version = "4.2.0b9"
10
10
  base_url = "ipfabric"
11
11
  min_version = "4.2.4"
12
12
 
@@ -46,22 +46,6 @@ exclude_fields = [
46
46
  "status",
47
47
  ]
48
48
 
49
-
50
- class IPFSiteChoiceField(forms.MultipleChoiceField):
51
- def valid_value(self, value):
52
- """Check to see if the provided value is a valid choice."""
53
- text_value = str(value)
54
- for k, v in self.choices:
55
- if isinstance(v, (list, tuple)):
56
- for k2, v2 in v:
57
- if value == k2 or text_value == str(k2):
58
- return True
59
- else:
60
- if value == k or text_value == str(k):
61
- return True
62
- return False
63
-
64
-
65
49
  dcim_parameters = {
66
50
  "site": forms.BooleanField(required=False, label=_("Sites"), initial=True),
67
51
  "manufacturer": forms.BooleanField(
@@ -78,43 +62,45 @@ dcim_parameters = {
78
62
  "virtualchassis": forms.BooleanField(
79
63
  required=False, label=_("Virtual Chassis"), initial=True
80
64
  ),
81
- "interface": forms.BooleanField(required=False, label=_("Interfaces")),
82
- "macaddress": forms.BooleanField(required=False, label=_("MAC Addresses")),
83
- "inventoryitem": forms.BooleanField(required=False, label=_("Part Numbers")),
65
+ "interface": forms.BooleanField(
66
+ required=False, label=_("Interfaces"), initial=False
67
+ ),
68
+ "macaddress": forms.BooleanField(
69
+ required=False, label=_("MAC Addresses"), initial=False
70
+ ),
71
+ "inventoryitem": forms.BooleanField(
72
+ required=False, label=_("Part Numbers"), initial=False
73
+ ),
84
74
  }
85
75
  ipam_parameters = {
86
- "vlan": forms.BooleanField(required=False, label=_("VLANs")),
87
- "vrf": forms.BooleanField(required=False, label=_("VRFs")),
88
- "prefix": forms.BooleanField(required=False, label=_("Prefixes")),
89
- "ipaddress": forms.BooleanField(required=False, label=_("IP Addresses")),
76
+ "vlan": forms.BooleanField(required=False, label=_("VLANs"), initial=False),
77
+ "vrf": forms.BooleanField(required=False, label=_("VRFs"), initial=False),
78
+ "prefix": forms.BooleanField(required=False, label=_("Prefixes"), initial=False),
79
+ "ipaddress": forms.BooleanField(
80
+ required=False, label=_("IP Addresses"), initial=False
81
+ ),
90
82
  }
91
83
  sync_parameters = {"dcim": dcim_parameters, "ipam": ipam_parameters}
92
84
 
93
85
 
94
- def source_column_choices(model):
86
+ def source_column_choices(model: str) -> list[tuple[str, str]]:
95
87
  columns = transform_field_source_columns.get(model, None)
96
88
  if columns:
97
89
  choices = [(f, f) for f in transform_field_source_columns.get(model)]
98
90
  else:
99
- choices = []
91
+ # This should never happen, but better be safe than sorry
92
+ choices = [] # pragma: no cover
100
93
  return choices
101
94
 
102
95
 
103
- def add_all_sites(choices):
104
- """
105
- Add a blank choice to the beginning of a choices list.
106
- """
107
- return ((None, "All Sites"),) + tuple(choices)
108
-
109
-
110
- def str_to_list(str):
111
- if not isinstance(str, list):
112
- return [str]
96
+ def str_to_list(_str: str | list) -> list[str]:
97
+ if not isinstance(_str, list):
98
+ return [_str]
113
99
  else:
114
- return str
100
+ return _str
115
101
 
116
102
 
117
- def list_to_choices(choices):
103
+ def list_to_choices(choices: list[str]) -> tuple[tuple[str, str], ...]:
118
104
  new_choices = ()
119
105
  for choice in choices:
120
106
  new_choices = new_choices + ((choice, choice),)
@@ -176,7 +162,7 @@ class IPFabricRelationshipFieldForm(NetBoxModelForm):
176
162
  )
177
163
  self.fields["target_field"].widget.initial = self.instance.target_field
178
164
  else:
179
- if kwargs["initial"].get("transform_map", None):
165
+ if kwargs.get("initial", {}).get("transform_map", None):
180
166
  transform_map_id = kwargs["initial"]["transform_map"]
181
167
  transform_map = IPFabricTransformMap.objects.get(
182
168
  pk=transform_map_id
@@ -258,7 +244,7 @@ class IPFabricTransformFieldForm(NetBoxModelForm):
258
244
  source_column_choices(source_fields)
259
245
  )
260
246
  else:
261
- if kwargs["initial"].get("transform_map", None):
247
+ if kwargs.get("initial", {}).get("transform_map", None):
262
248
  transform_map_id = kwargs["initial"]["transform_map"]
263
249
  transform_map = IPFabricTransformMap.objects.get(
264
250
  pk=transform_map_id
@@ -375,22 +361,22 @@ class IPFabricSourceForm(NetBoxModelForm):
375
361
  "type": HTMXSelect(),
376
362
  }
377
363
 
378
- @property
379
- def fieldsets(self):
380
- fieldsets = [
381
- FieldSet("name", "type", "url", name=_("Source")),
382
- FieldSet("timeout", name=_("Parameters")),
383
- ]
384
-
385
- if self.source_type == "local":
386
- fieldsets[1] = FieldSet("auth", "verify", "timeout", name=_("Parameters"))
387
-
388
- return fieldsets
389
-
390
364
  def __init__(self, *args, **kwargs):
391
365
  super().__init__(*args, **kwargs)
392
366
  self.source_type = get_field_value(self, "type")
393
367
 
368
+ # Set fieldsets dynamically based on source_type
369
+ if self.source_type == "local":
370
+ self.fieldsets = [
371
+ FieldSet("name", "type", "url", name=_("Source")),
372
+ FieldSet("auth", "verify", "timeout", name=_("Parameters")),
373
+ ]
374
+ else:
375
+ self.fieldsets = [
376
+ FieldSet("name", "type", "url", name=_("Source")),
377
+ FieldSet("timeout", name=_("Parameters")),
378
+ ]
379
+
394
380
  self.fields["timeout"] = forms.IntegerField(
395
381
  required=False,
396
382
  label=_("Timeout"),
@@ -451,6 +437,9 @@ class OrderedModelMultipleChoiceField(forms.ModelMultipleChoiceField):
451
437
 
452
438
  def clean(self, value):
453
439
  qs = super().clean(value)
440
+ # Handle None or empty values
441
+ if not value:
442
+ return qs
454
443
  clauses = " ".join(
455
444
  ["WHEN id=%s THEN %s" % (pk, i) for i, pk in enumerate(value)]
456
445
  )
@@ -534,32 +523,6 @@ class IPFabricSyncForm(NetBoxModelForm):
534
523
  )
535
524
  widgets = {"source": HTMXSelect()}
536
525
 
537
- @property
538
- def fieldsets(self):
539
- fieldsets = [
540
- FieldSet("name", "source", "groups", name=_("IP Fabric Source")),
541
- ]
542
- if self.source_type == "local":
543
- fieldsets.append(
544
- FieldSet("snapshot_data", "sites", name=_("Snapshot Information")),
545
- )
546
- else:
547
- fieldsets.append(
548
- FieldSet("snapshot_data", name=_("Snapshot Information")),
549
- )
550
- if self.backend_fields:
551
- for k, v in self.backend_fields.items():
552
- fieldsets.append(FieldSet(*v, name=f"{k.upper()} Parameters"))
553
- fieldsets.append(
554
- FieldSet("scheduled", "interval", name=_("Ingestion Execution Parameters"))
555
- )
556
- fieldsets.append(
557
- FieldSet("auto_merge", "update_custom_fields", name=_("Extras"))
558
- )
559
- fieldsets.append(FieldSet("tags", name=_("Tags")))
560
-
561
- return fieldsets
562
-
563
526
  def __init__(self, *args, **kwargs):
564
527
  super().__init__(*args, **kwargs)
565
528
  self.source_type = None
@@ -590,15 +553,13 @@ class IPFabricSyncForm(NetBoxModelForm):
590
553
  self.initial["sites"] = self.instance.parameters.get("sites", [])
591
554
  self.initial["groups"] = self.instance.parameters.get("groups", [])
592
555
 
593
- backend = sync_parameters
594
-
595
556
  now = local_now().strftime("%Y-%m-%d %H:%M:%S")
596
557
  self.fields["scheduled"].help_text += f" (current time: <strong>{now}</strong>)"
597
558
 
598
559
  # Add backend-specific form fields
599
560
  self.backend_fields = {}
600
561
 
601
- for k, v in backend.items():
562
+ for k, v in sync_parameters.items():
602
563
  self.backend_fields[k] = []
603
564
  for name, form_field in v.items():
604
565
  field_name = f"ipf_{name}"
@@ -607,17 +568,52 @@ class IPFabricSyncForm(NetBoxModelForm):
607
568
  if self.instance and self.instance.parameters:
608
569
  self.fields[field_name].initial = self.instance.parameters.get(name)
609
570
 
571
+ # Set fieldsets dynamically based and backend_fields
572
+ fieldsets = [
573
+ FieldSet("name", "source", "groups", name=_("IP Fabric Source")),
574
+ ]
575
+ if self.source_type == "local":
576
+ fieldsets.append(
577
+ FieldSet("snapshot_data", "sites", name=_("Snapshot Information")),
578
+ )
579
+ else:
580
+ fieldsets.append(
581
+ FieldSet("snapshot_data", name=_("Snapshot Information")),
582
+ )
583
+ for k, v in self.backend_fields.items():
584
+ fieldsets.append(FieldSet(*v, name=f"{k.upper()} Parameters"))
585
+ fieldsets.append(
586
+ FieldSet("scheduled", "interval", name=_("Ingestion Execution Parameters"))
587
+ )
588
+ fieldsets.append(
589
+ FieldSet("auto_merge", "update_custom_fields", name=_("Extras"))
590
+ )
591
+ fieldsets.append(FieldSet("tags", name=_("Tags")))
592
+
593
+ self.fieldsets = fieldsets
594
+
610
595
  def clean(self):
611
596
  super().clean()
612
- snapshot = self.cleaned_data["snapshot_data"]
613
597
 
614
598
  sites = self.data.get("sites")
615
- choices = list_to_choices(str_to_list(sites))
616
- self.fields["sites"].choices = choices
617
-
618
- if sites:
619
- if not any(y in x for x in snapshot.sites for y in sites):
620
- raise ValidationError({"sites": f"{sites} not part of the snapshot."})
599
+ self.fields["sites"].choices = list_to_choices(str_to_list(sites))
600
+ if sites and "snapshot_data" in self.cleaned_data:
601
+ snapshot = self.cleaned_data["snapshot_data"]
602
+ # Check if all sites are valid - fail if any site is not found in snapshot.sites
603
+ if not all(
604
+ any(site in snapshot_site for snapshot_site in snapshot.sites)
605
+ for site in sites
606
+ ):
607
+ invalid_sites = [
608
+ site
609
+ for site in sites
610
+ if not any(
611
+ site in snapshot_site for snapshot_site in snapshot.sites
612
+ )
613
+ ]
614
+ raise ValidationError(
615
+ {"sites": f"Sites {invalid_sites} not part of the snapshot."}
616
+ )
621
617
 
622
618
  scheduled_time = self.cleaned_data.get("scheduled")
623
619
  if scheduled_time and scheduled_time < local_now():
@@ -665,50 +661,6 @@ class IPFabricSyncForm(NetBoxModelForm):
665
661
  return object
666
662
 
667
663
 
668
- # class SyncForm(forms.Form):
669
- # def __init__(self, *args, **kwargs):
670
- # self.snapshots = kwargs.pop("snapshot_choices", None)
671
- # self.sites = kwargs.pop("site_choices", None)
672
- # super(SyncForm, self).__init__(*args, **kwargs)
673
- # if self.snapshots:
674
- # snapshot_choices = [
675
- # (snapshot_id, snapshot_name)
676
- # for snapshot_name, snapshot_id in self.snapshots.values()
677
- # ]
678
- # self.fields["snapshot"] = forms.ChoiceField(
679
- # label="Snapshot",
680
- # required=True,
681
- # choices=snapshot_choices,
682
- # help_text="IPFabric snapshot to sync from. Defaults to $last",
683
- # widget=forms.Select(
684
- # attrs={
685
- # "hx-get": reverse("plugins:ipfabric_netbox:ipfabricsync_add"),
686
- # "hx-trigger": "change",
687
- # "hx-target": "#modules",
688
- # "class": "form-control",
689
- # }
690
- # ),
691
- # )
692
- # if self.sites:
693
- # site_choices = [(site, site) for site in self.sites]
694
- # self.fields["site"] = forms.ChoiceField(
695
- # label="Site",
696
- # required=False,
697
- # choices=add_blank_choice(site_choices),
698
- # help_text="Sites available within snapshot",
699
- # widget=forms.Select(attrs={"class": "form-control"}),
700
- # )
701
- # else:
702
- # self.fields["site"] = forms.ChoiceField(
703
- # label="Site",
704
- # required=False,
705
- # choices=add_blank_choice([]),
706
- # help_text="Sites available within snapshot",
707
- # widget=forms.Select(
708
- # attrs={"class": "form-control", "disabled": "disabled"}
709
- # ),
710
- # )
711
-
712
664
  tableChoices = [
713
665
  ("eol_details", "Inventory - EOL_DETAILS"),
714
666
  ("fans", "Inventory - FANS"),