ipfabric_netbox 4.2.2__py3-none-any.whl → 4.2.2b2__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 (30) hide show
  1. ipfabric_netbox/__init__.py +1 -1
  2. ipfabric_netbox/api/__init__.py +0 -1
  3. ipfabric_netbox/api/serializers.py +147 -90
  4. ipfabric_netbox/api/urls.py +4 -4
  5. ipfabric_netbox/api/views.py +18 -19
  6. ipfabric_netbox/choices.py +0 -12
  7. ipfabric_netbox/filtersets.py +67 -4
  8. ipfabric_netbox/forms.py +99 -140
  9. ipfabric_netbox/graphql/__init__.py +23 -0
  10. ipfabric_netbox/graphql/enums.py +35 -0
  11. ipfabric_netbox/graphql/filters.py +317 -0
  12. ipfabric_netbox/graphql/schema.py +101 -0
  13. ipfabric_netbox/graphql/types.py +216 -0
  14. ipfabric_netbox/migrations/0016_tags_and_changelog_for_snapshots.py +31 -0
  15. ipfabric_netbox/migrations/0017_ipfabricsync_update_custom_fields.py +17 -0
  16. ipfabric_netbox/migrations/0018_remove_type_field.py +17 -0
  17. ipfabric_netbox/models.py +10 -10
  18. ipfabric_netbox/tables.py +30 -9
  19. ipfabric_netbox/templates/ipfabric_netbox/ipfabricsource.html +1 -1
  20. ipfabric_netbox/tests/api/__init__.py +0 -0
  21. ipfabric_netbox/tests/api/test_api.py +879 -0
  22. ipfabric_netbox/tests/test_forms.py +1440 -0
  23. ipfabric_netbox/tests/test_models.py +47 -11
  24. ipfabric_netbox/utilities/ipfutils.py +43 -23
  25. ipfabric_netbox/views.py +6 -8
  26. {ipfabric_netbox-4.2.2.dist-info → ipfabric_netbox-4.2.2b2.dist-info}/METADATA +8 -7
  27. {ipfabric_netbox-4.2.2.dist-info → ipfabric_netbox-4.2.2b2.dist-info}/RECORD +28 -19
  28. {ipfabric_netbox-4.2.2.dist-info → ipfabric_netbox-4.2.2b2.dist-info}/WHEEL +1 -1
  29. ipfabric_netbox/api/nested_serializers.py +0 -78
  30. ipfabric_netbox/templates/ipfabric_netbox/ipfabricsync_list.html +0 -71
ipfabric_netbox/forms.py CHANGED
@@ -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=True
67
+ ),
68
+ "macaddress": forms.BooleanField(
69
+ required=False, label=_("MAC Addresses"), initial=True
70
+ ),
71
+ "inventoryitem": forms.BooleanField(
72
+ required=False, label=_("Part Numbers"), initial=True
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=True),
77
+ "vrf": forms.BooleanField(required=False, label=_("VRFs"), initial=True),
78
+ "prefix": forms.BooleanField(required=False, label=_("Prefixes"), initial=True),
79
+ "ipaddress": forms.BooleanField(
80
+ required=False, label=_("IP Addresses"), initial=True
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,29 @@ 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
+ self.fieldsets: list[FieldSet] = []
370
+ self.fieldsets.append(FieldSet("name", "type", "url", name=_("Source")))
371
+ if self.source_type == "local":
372
+ self.fieldsets.append(
373
+ FieldSet("auth", "verify", "timeout", name=_("Parameters"))
374
+ )
375
+ else:
376
+ self.fieldsets.append(FieldSet("timeout", name=_("Parameters")))
377
+
378
+ self.fields["url"] = forms.URLField(
379
+ required=True,
380
+ label=_("Base URL"),
381
+ widget=forms.TextInput(attrs={"class": "form-control"}),
382
+ help_text=_(
383
+ "for example https://myinstance.com or https://192.168.0.1 etc."
384
+ ),
385
+ )
386
+
394
387
  self.fields["timeout"] = forms.IntegerField(
395
388
  required=False,
396
389
  label=_("Timeout"),
@@ -451,6 +444,9 @@ class OrderedModelMultipleChoiceField(forms.ModelMultipleChoiceField):
451
444
 
452
445
  def clean(self, value):
453
446
  qs = super().clean(value)
447
+ # Handle None or empty values
448
+ if not value:
449
+ return qs
454
450
  clauses = " ".join(
455
451
  ["WHEN id=%s THEN %s" % (pk, i) for i, pk in enumerate(value)]
456
452
  )
@@ -488,11 +484,18 @@ class IPFabricSyncForm(NetBoxModelForm):
488
484
  sites = forms.MultipleChoiceField(
489
485
  required=False,
490
486
  label=_("Sites"),
487
+ help_text=_("Defaults to all sites if none selected."),
491
488
  widget=APISelectMultiple(
492
489
  api_url="/api/plugins/ipfabric/snapshot/{{snapshot_data}}/sites/",
493
490
  ),
494
491
  )
495
492
 
493
+ update_custom_fields = forms.BooleanField(
494
+ required=False,
495
+ label=_("Custom Fields Updating"),
496
+ help_text=_("Update object custom fields where applicable."),
497
+ )
498
+
496
499
  scheduled = forms.DateTimeField(
497
500
  required=False,
498
501
  widget=DateTimePicker(),
@@ -519,41 +522,13 @@ class IPFabricSyncForm(NetBoxModelForm):
519
522
  "source",
520
523
  "snapshot_data",
521
524
  "auto_merge",
525
+ "update_custom_fields",
522
526
  "sites",
523
- "type",
524
527
  "tags",
525
528
  "scheduled",
526
529
  "interval",
527
530
  )
528
- widgets = {
529
- "source": HTMXSelect(),
530
- "type": HTMXSelect(),
531
- }
532
-
533
- @property
534
- def fieldsets(self):
535
- fieldsets = [
536
- FieldSet("name", "source", "groups", name=_("IP Fabric Source")),
537
- ]
538
- if self.source_type == "local":
539
- fieldsets.append(
540
- FieldSet("snapshot_data", "sites", name=_("Snapshot Information")),
541
- )
542
- else:
543
- fieldsets.append(
544
- FieldSet("snapshot_data", name=_("Snapshot Information")),
545
- )
546
- fieldsets.append(FieldSet("type", name=_("Ingestion Type")))
547
- if self.backend_fields:
548
- for k, v in self.backend_fields.items():
549
- fieldsets.append(FieldSet(*v, name=f"{k.upper()} Parameters"))
550
- fieldsets.append(
551
- FieldSet("scheduled", "interval", name=_("Ingestion Execution Parameters"))
552
- )
553
- fieldsets.append(FieldSet("auto_merge", name=_("Extras")))
554
- fieldsets.append(FieldSet("tags", name=_("Tags")))
555
-
556
- return fieldsets
531
+ widgets = {"source": HTMXSelect()}
557
532
 
558
533
  def __init__(self, *args, **kwargs):
559
534
  super().__init__(*args, **kwargs)
@@ -585,20 +560,13 @@ class IPFabricSyncForm(NetBoxModelForm):
585
560
  self.initial["sites"] = self.instance.parameters.get("sites", [])
586
561
  self.initial["groups"] = self.instance.parameters.get("groups", [])
587
562
 
588
- backend_type = get_field_value(self, "type")
589
- backend = {}
590
- if backend_type == "all":
591
- backend = sync_parameters
592
- else:
593
- backend[backend_type] = sync_parameters.get(backend_type)
594
-
595
563
  now = local_now().strftime("%Y-%m-%d %H:%M:%S")
596
564
  self.fields["scheduled"].help_text += f" (current time: <strong>{now}</strong>)"
597
565
 
598
566
  # Add backend-specific form fields
599
567
  self.backend_fields = {}
600
568
 
601
- for k, v in backend.items():
569
+ for k, v in sync_parameters.items():
602
570
  self.backend_fields[k] = []
603
571
  for name, form_field in v.items():
604
572
  field_name = f"ipf_{name}"
@@ -607,17 +575,52 @@ class IPFabricSyncForm(NetBoxModelForm):
607
575
  if self.instance and self.instance.parameters:
608
576
  self.fields[field_name].initial = self.instance.parameters.get(name)
609
577
 
578
+ # Set fieldsets dynamically based and backend_fields
579
+ fieldsets = [
580
+ FieldSet("name", "source", "groups", name=_("IP Fabric Source")),
581
+ ]
582
+ if self.source_type == "local":
583
+ fieldsets.append(
584
+ FieldSet("snapshot_data", "sites", name=_("Snapshot Information")),
585
+ )
586
+ else:
587
+ fieldsets.append(
588
+ FieldSet("snapshot_data", name=_("Snapshot Information")),
589
+ )
590
+ for k, v in self.backend_fields.items():
591
+ fieldsets.append(FieldSet(*v, name=f"{k.upper()} Parameters"))
592
+ fieldsets.append(
593
+ FieldSet("scheduled", "interval", name=_("Ingestion Execution Parameters"))
594
+ )
595
+ fieldsets.append(
596
+ FieldSet("auto_merge", "update_custom_fields", name=_("Extras"))
597
+ )
598
+ fieldsets.append(FieldSet("tags", name=_("Tags")))
599
+
600
+ self.fieldsets = fieldsets
601
+
610
602
  def clean(self):
611
603
  super().clean()
612
- snapshot = self.cleaned_data["snapshot_data"]
613
604
 
614
605
  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."})
606
+ self.fields["sites"].choices = list_to_choices(str_to_list(sites))
607
+ if sites and "snapshot_data" in self.cleaned_data:
608
+ snapshot = self.cleaned_data["snapshot_data"]
609
+ # Check if all sites are valid - fail if any site is not found in snapshot.sites
610
+ if not all(
611
+ any(site in snapshot_site for snapshot_site in snapshot.sites)
612
+ for site in sites
613
+ ):
614
+ invalid_sites = [
615
+ site
616
+ for site in sites
617
+ if not any(
618
+ site in snapshot_site for snapshot_site in snapshot.sites
619
+ )
620
+ ]
621
+ raise ValidationError(
622
+ {"sites": f"Sites {invalid_sites} not part of the snapshot."}
623
+ )
621
624
 
622
625
  scheduled_time = self.cleaned_data.get("scheduled")
623
626
  if scheduled_time and scheduled_time < local_now():
@@ -665,50 +668,6 @@ class IPFabricSyncForm(NetBoxModelForm):
665
668
  return object
666
669
 
667
670
 
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
671
  tableChoices = [
713
672
  ("eol_details", "Inventory - EOL_DETAILS"),
714
673
  ("fans", "Inventory - FANS"),
@@ -0,0 +1,23 @@
1
+ from .schema import IPFabricDataQuery
2
+ from .schema import IPFabricIngestionIssueQuery
3
+ from .schema import IPFabricIngestionQuery
4
+ from .schema import IPFabricRelationshipFieldQuery
5
+ from .schema import IPFabricSnapshotQuery
6
+ from .schema import IPFabricSourceQuery
7
+ from .schema import IPFabricSyncQuery
8
+ from .schema import IPFabricTransformFieldQuery
9
+ from .schema import IPFabricTransformMapGroupQuery
10
+ from .schema import IPFabricTransformMapQuery
11
+
12
+ schema = [
13
+ IPFabricTransformMapGroupQuery,
14
+ IPFabricTransformMapQuery,
15
+ IPFabricTransformFieldQuery,
16
+ IPFabricRelationshipFieldQuery,
17
+ IPFabricSourceQuery,
18
+ IPFabricSnapshotQuery,
19
+ IPFabricSyncQuery,
20
+ IPFabricIngestionQuery,
21
+ IPFabricIngestionIssueQuery,
22
+ IPFabricDataQuery,
23
+ ]
@@ -0,0 +1,35 @@
1
+ import strawberry
2
+ from core.choices import DataSourceStatusChoices
3
+ from core.choices import JobStatusChoices
4
+ from netbox_branching.choices import BranchStatusChoices
5
+
6
+ from ipfabric_netbox.choices import IPFabricRawDataTypeChoices
7
+ from ipfabric_netbox.choices import IPFabricSnapshotStatusModelChoices
8
+ from ipfabric_netbox.choices import IPFabricSourceTypeChoices
9
+ from ipfabric_netbox.choices import IPFabricTransformMapSourceModelChoices
10
+
11
+ __all__ = (
12
+ "DataSourceStatusEnum",
13
+ "IPFabricTransformMapSourceModelEnum",
14
+ "IPFabricSourceTypeEnum",
15
+ "IPFabricSnapshotStatusModelEnum",
16
+ "IPFabricRawDataTypeEnum",
17
+ "BranchStatusEnum",
18
+ "JobStatusEnum",
19
+ )
20
+
21
+ DataSourceStatusEnum = strawberry.enum(DataSourceStatusChoices.as_enum(prefix="type"))
22
+ IPFabricTransformMapSourceModelEnum = strawberry.enum(
23
+ IPFabricTransformMapSourceModelChoices.as_enum(prefix="type")
24
+ )
25
+ IPFabricSourceTypeEnum = strawberry.enum(
26
+ IPFabricSourceTypeChoices.as_enum(prefix="type")
27
+ )
28
+ IPFabricSnapshotStatusModelEnum = strawberry.enum(
29
+ IPFabricSnapshotStatusModelChoices.as_enum(prefix="type")
30
+ )
31
+ IPFabricRawDataTypeEnum = strawberry.enum(
32
+ IPFabricRawDataTypeChoices.as_enum(prefix="type")
33
+ )
34
+ BranchStatusEnum = strawberry.enum(BranchStatusChoices.as_enum(prefix="type"))
35
+ JobStatusEnum = strawberry.enum(JobStatusChoices.as_enum(prefix="type"))