ipfabric_netbox 4.2.1b3__py3-none-any.whl → 4.2.2__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/__init__.py +1 -0
- ipfabric_netbox/api/nested_serializers.py +78 -0
- ipfabric_netbox/api/serializers.py +90 -147
- ipfabric_netbox/api/urls.py +4 -4
- ipfabric_netbox/api/views.py +19 -18
- ipfabric_netbox/choices.py +12 -0
- ipfabric_netbox/filtersets.py +4 -67
- ipfabric_netbox/forms.py +140 -99
- ipfabric_netbox/models.py +10 -10
- ipfabric_netbox/tables.py +9 -30
- ipfabric_netbox/templates/ipfabric_netbox/ipfabricsource.html +1 -1
- ipfabric_netbox/templates/ipfabric_netbox/ipfabricsync_list.html +71 -0
- ipfabric_netbox/tests/test_models.py +11 -47
- ipfabric_netbox/utilities/ipfutils.py +23 -43
- ipfabric_netbox/views.py +13 -17
- {ipfabric_netbox-4.2.1b3.dist-info → ipfabric_netbox-4.2.2.dist-info}/METADATA +6 -6
- {ipfabric_netbox-4.2.1b3.dist-info → ipfabric_netbox-4.2.2.dist-info}/RECORD +19 -28
- ipfabric_netbox/graphql/__init__.py +0 -23
- ipfabric_netbox/graphql/enums.py +0 -35
- ipfabric_netbox/graphql/filters.py +0 -317
- ipfabric_netbox/graphql/schema.py +0 -101
- ipfabric_netbox/graphql/types.py +0 -216
- ipfabric_netbox/migrations/0016_tags_and_changelog_for_snapshots.py +0 -31
- ipfabric_netbox/migrations/0017_ipfabricsync_update_custom_fields.py +0 -17
- ipfabric_netbox/migrations/0018_remove_type_field.py +0 -17
- ipfabric_netbox/tests/api/__init__.py +0 -0
- ipfabric_netbox/tests/api/test_api.py +0 -879
- ipfabric_netbox/tests/test_forms.py +0 -1440
- {ipfabric_netbox-4.2.1b3.dist-info → ipfabric_netbox-4.2.2.dist-info}/WHEEL +0 -0
ipfabric_netbox/forms.py
CHANGED
|
@@ -46,6 +46,22 @@ 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
|
+
|
|
49
65
|
dcim_parameters = {
|
|
50
66
|
"site": forms.BooleanField(required=False, label=_("Sites"), initial=True),
|
|
51
67
|
"manufacturer": forms.BooleanField(
|
|
@@ -62,45 +78,43 @@ dcim_parameters = {
|
|
|
62
78
|
"virtualchassis": forms.BooleanField(
|
|
63
79
|
required=False, label=_("Virtual Chassis"), initial=True
|
|
64
80
|
),
|
|
65
|
-
"interface": forms.BooleanField(
|
|
66
|
-
|
|
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
|
-
),
|
|
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")),
|
|
74
84
|
}
|
|
75
85
|
ipam_parameters = {
|
|
76
|
-
"vlan": forms.BooleanField(required=False, label=_("VLANs")
|
|
77
|
-
"vrf": forms.BooleanField(required=False, label=_("VRFs")
|
|
78
|
-
"prefix": forms.BooleanField(required=False, label=_("Prefixes")
|
|
79
|
-
"ipaddress": forms.BooleanField(
|
|
80
|
-
required=False, label=_("IP Addresses"), initial=False
|
|
81
|
-
),
|
|
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")),
|
|
82
90
|
}
|
|
83
91
|
sync_parameters = {"dcim": dcim_parameters, "ipam": ipam_parameters}
|
|
84
92
|
|
|
85
93
|
|
|
86
|
-
def source_column_choices(model
|
|
94
|
+
def source_column_choices(model):
|
|
87
95
|
columns = transform_field_source_columns.get(model, None)
|
|
88
96
|
if columns:
|
|
89
97
|
choices = [(f, f) for f in transform_field_source_columns.get(model)]
|
|
90
98
|
else:
|
|
91
|
-
|
|
92
|
-
choices = [] # pragma: no cover
|
|
99
|
+
choices = []
|
|
93
100
|
return choices
|
|
94
101
|
|
|
95
102
|
|
|
96
|
-
def
|
|
97
|
-
|
|
98
|
-
|
|
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]
|
|
99
113
|
else:
|
|
100
|
-
return
|
|
114
|
+
return str
|
|
101
115
|
|
|
102
116
|
|
|
103
|
-
def list_to_choices(choices
|
|
117
|
+
def list_to_choices(choices):
|
|
104
118
|
new_choices = ()
|
|
105
119
|
for choice in choices:
|
|
106
120
|
new_choices = new_choices + ((choice, choice),)
|
|
@@ -162,7 +176,7 @@ class IPFabricRelationshipFieldForm(NetBoxModelForm):
|
|
|
162
176
|
)
|
|
163
177
|
self.fields["target_field"].widget.initial = self.instance.target_field
|
|
164
178
|
else:
|
|
165
|
-
if kwargs
|
|
179
|
+
if kwargs["initial"].get("transform_map", None):
|
|
166
180
|
transform_map_id = kwargs["initial"]["transform_map"]
|
|
167
181
|
transform_map = IPFabricTransformMap.objects.get(
|
|
168
182
|
pk=transform_map_id
|
|
@@ -244,7 +258,7 @@ class IPFabricTransformFieldForm(NetBoxModelForm):
|
|
|
244
258
|
source_column_choices(source_fields)
|
|
245
259
|
)
|
|
246
260
|
else:
|
|
247
|
-
if kwargs
|
|
261
|
+
if kwargs["initial"].get("transform_map", None):
|
|
248
262
|
transform_map_id = kwargs["initial"]["transform_map"]
|
|
249
263
|
transform_map = IPFabricTransformMap.objects.get(
|
|
250
264
|
pk=transform_map_id
|
|
@@ -361,29 +375,22 @@ class IPFabricSourceForm(NetBoxModelForm):
|
|
|
361
375
|
"type": HTMXSelect(),
|
|
362
376
|
}
|
|
363
377
|
|
|
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
|
+
|
|
364
390
|
def __init__(self, *args, **kwargs):
|
|
365
391
|
super().__init__(*args, **kwargs)
|
|
366
392
|
self.source_type = get_field_value(self, "type")
|
|
367
393
|
|
|
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
|
-
|
|
387
394
|
self.fields["timeout"] = forms.IntegerField(
|
|
388
395
|
required=False,
|
|
389
396
|
label=_("Timeout"),
|
|
@@ -444,9 +451,6 @@ class OrderedModelMultipleChoiceField(forms.ModelMultipleChoiceField):
|
|
|
444
451
|
|
|
445
452
|
def clean(self, value):
|
|
446
453
|
qs = super().clean(value)
|
|
447
|
-
# Handle None or empty values
|
|
448
|
-
if not value:
|
|
449
|
-
return qs
|
|
450
454
|
clauses = " ".join(
|
|
451
455
|
["WHEN id=%s THEN %s" % (pk, i) for i, pk in enumerate(value)]
|
|
452
456
|
)
|
|
@@ -484,18 +488,11 @@ class IPFabricSyncForm(NetBoxModelForm):
|
|
|
484
488
|
sites = forms.MultipleChoiceField(
|
|
485
489
|
required=False,
|
|
486
490
|
label=_("Sites"),
|
|
487
|
-
help_text=_("Defaults to all sites if none selected."),
|
|
488
491
|
widget=APISelectMultiple(
|
|
489
492
|
api_url="/api/plugins/ipfabric/snapshot/{{snapshot_data}}/sites/",
|
|
490
493
|
),
|
|
491
494
|
)
|
|
492
495
|
|
|
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
|
-
|
|
499
496
|
scheduled = forms.DateTimeField(
|
|
500
497
|
required=False,
|
|
501
498
|
widget=DateTimePicker(),
|
|
@@ -522,13 +519,41 @@ class IPFabricSyncForm(NetBoxModelForm):
|
|
|
522
519
|
"source",
|
|
523
520
|
"snapshot_data",
|
|
524
521
|
"auto_merge",
|
|
525
|
-
"update_custom_fields",
|
|
526
522
|
"sites",
|
|
523
|
+
"type",
|
|
527
524
|
"tags",
|
|
528
525
|
"scheduled",
|
|
529
526
|
"interval",
|
|
530
527
|
)
|
|
531
|
-
widgets = {
|
|
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
|
|
532
557
|
|
|
533
558
|
def __init__(self, *args, **kwargs):
|
|
534
559
|
super().__init__(*args, **kwargs)
|
|
@@ -560,13 +585,20 @@ class IPFabricSyncForm(NetBoxModelForm):
|
|
|
560
585
|
self.initial["sites"] = self.instance.parameters.get("sites", [])
|
|
561
586
|
self.initial["groups"] = self.instance.parameters.get("groups", [])
|
|
562
587
|
|
|
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
|
+
|
|
563
595
|
now = local_now().strftime("%Y-%m-%d %H:%M:%S")
|
|
564
596
|
self.fields["scheduled"].help_text += f" (current time: <strong>{now}</strong>)"
|
|
565
597
|
|
|
566
598
|
# Add backend-specific form fields
|
|
567
599
|
self.backend_fields = {}
|
|
568
600
|
|
|
569
|
-
for k, v in
|
|
601
|
+
for k, v in backend.items():
|
|
570
602
|
self.backend_fields[k] = []
|
|
571
603
|
for name, form_field in v.items():
|
|
572
604
|
field_name = f"ipf_{name}"
|
|
@@ -575,52 +607,17 @@ class IPFabricSyncForm(NetBoxModelForm):
|
|
|
575
607
|
if self.instance and self.instance.parameters:
|
|
576
608
|
self.fields[field_name].initial = self.instance.parameters.get(name)
|
|
577
609
|
|
|
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
|
-
|
|
602
610
|
def clean(self):
|
|
603
611
|
super().clean()
|
|
612
|
+
snapshot = self.cleaned_data["snapshot_data"]
|
|
604
613
|
|
|
605
614
|
sites = self.data.get("sites")
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
if not
|
|
611
|
-
|
|
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
|
-
)
|
|
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."})
|
|
624
621
|
|
|
625
622
|
scheduled_time = self.cleaned_data.get("scheduled")
|
|
626
623
|
if scheduled_time and scheduled_time < local_now():
|
|
@@ -668,6 +665,50 @@ class IPFabricSyncForm(NetBoxModelForm):
|
|
|
668
665
|
return object
|
|
669
666
|
|
|
670
667
|
|
|
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
|
+
|
|
671
712
|
tableChoices = [
|
|
672
713
|
("eol_details", "Inventory - EOL_DETAILS"),
|
|
673
714
|
("fans", "Inventory - FANS"),
|
ipfabric_netbox/models.py
CHANGED
|
@@ -44,6 +44,7 @@ from utilities.request import NetBoxFakeRequest
|
|
|
44
44
|
from .choices import IPFabricRawDataTypeChoices
|
|
45
45
|
from .choices import IPFabricSnapshotStatusModelChoices
|
|
46
46
|
from .choices import IPFabricSourceTypeChoices
|
|
47
|
+
from .choices import IPFabricSyncTypeChoices
|
|
47
48
|
from .choices import IPFabricTransformMapSourceModelChoices
|
|
48
49
|
from .choices import required_transform_map_contenttypes
|
|
49
50
|
from .signals import clear_other_primary_ip
|
|
@@ -544,7 +545,9 @@ class IPFabricSource(IPFabricClient, JobsMixin, PrimaryModel):
|
|
|
544
545
|
# post_sync.send(sender=self.__class__, instance=self)
|
|
545
546
|
|
|
546
547
|
|
|
547
|
-
class IPFabricSnapshot(
|
|
548
|
+
class IPFabricSnapshot(models.Model):
|
|
549
|
+
created = models.DateTimeField(auto_now_add=True)
|
|
550
|
+
last_updated = models.DateTimeField(editable=False)
|
|
548
551
|
source = models.ForeignKey(
|
|
549
552
|
to=IPFabricSource,
|
|
550
553
|
on_delete=models.CASCADE,
|
|
@@ -597,6 +600,11 @@ class IPFabricSync(IPFabricClient, JobsMixin, TagsMixin, ChangeLoggedModel):
|
|
|
597
600
|
on_delete=models.CASCADE,
|
|
598
601
|
related_name="snapshots",
|
|
599
602
|
)
|
|
603
|
+
type = models.CharField(
|
|
604
|
+
max_length=50,
|
|
605
|
+
choices=IPFabricSyncTypeChoices,
|
|
606
|
+
default=IPFabricSyncTypeChoices.DCIM,
|
|
607
|
+
)
|
|
600
608
|
status = models.CharField(
|
|
601
609
|
max_length=50,
|
|
602
610
|
choices=DataSourceStatusChoices,
|
|
@@ -605,7 +613,6 @@ class IPFabricSync(IPFabricClient, JobsMixin, TagsMixin, ChangeLoggedModel):
|
|
|
605
613
|
)
|
|
606
614
|
parameters = models.JSONField(blank=True, null=True)
|
|
607
615
|
auto_merge = models.BooleanField(default=False)
|
|
608
|
-
update_custom_fields = models.BooleanField(default=True)
|
|
609
616
|
last_synced = models.DateTimeField(blank=True, null=True, editable=False)
|
|
610
617
|
scheduled = models.DateTimeField(null=True, blank=True)
|
|
611
618
|
interval = models.PositiveIntegerField(
|
|
@@ -661,10 +668,6 @@ class IPFabricSync(IPFabricClient, JobsMixin, TagsMixin, ChangeLoggedModel):
|
|
|
661
668
|
else:
|
|
662
669
|
return False
|
|
663
670
|
|
|
664
|
-
@property
|
|
665
|
-
def last_ingestion(self):
|
|
666
|
-
return self.ipfabricingestion_set.last()
|
|
667
|
-
|
|
668
671
|
@staticmethod
|
|
669
672
|
def get_transform_maps(group_ids=None):
|
|
670
673
|
"""
|
|
@@ -886,10 +889,7 @@ class IPFabricIngestion(JobsMixin, models.Model):
|
|
|
886
889
|
def name(self):
|
|
887
890
|
if self.branch:
|
|
888
891
|
return self.branch.name
|
|
889
|
-
|
|
890
|
-
return f"{self.sync.name} (Ingestion {self.pk})"
|
|
891
|
-
except IPFabricIngestion.sync.RelatedObjectDoesNotExist:
|
|
892
|
-
return f"Ingestion {self.pk} (No Sync)"
|
|
892
|
+
return f"{self.sync.name} (Ingestion {self.pk})"
|
|
893
893
|
|
|
894
894
|
def get_absolute_url(self):
|
|
895
895
|
return reverse("plugins:ipfabric_netbox:ipfabricingestion", args=[self.pk])
|
ipfabric_netbox/tables.py
CHANGED
|
@@ -142,41 +142,20 @@ class IPFabricSourceTable(NetBoxTable):
|
|
|
142
142
|
default_columns = ("pk", "name", "status", "description", "snapshot_count")
|
|
143
143
|
|
|
144
144
|
|
|
145
|
-
class
|
|
146
|
-
|
|
145
|
+
class SyncTable(NetBoxTable):
|
|
146
|
+
actions = None
|
|
147
147
|
status = columns.ChoiceFieldColumn()
|
|
148
148
|
snapshot_name = tables.Column(
|
|
149
|
-
verbose_name="Snapshot Name",
|
|
150
|
-
accessor="snapshot_data",
|
|
151
|
-
linkify=True,
|
|
152
|
-
)
|
|
153
|
-
last_ingestion = tables.Column(
|
|
154
|
-
accessor="last_ingestion",
|
|
155
|
-
verbose_name="Last Ingestion",
|
|
156
|
-
linkify=True,
|
|
149
|
+
verbose_name="Snapshot Name", accessor="snapshot_data"
|
|
157
150
|
)
|
|
158
151
|
|
|
159
|
-
def
|
|
160
|
-
return
|
|
161
|
-
|
|
162
|
-
def render_snapshot_name(self, value: IPFabricSnapshot):
|
|
163
|
-
return getattr(value, "name", "---") if value else "---"
|
|
152
|
+
def render_snapshot_name(self, value):
|
|
153
|
+
return value.get("name", "---")
|
|
164
154
|
|
|
165
155
|
class Meta(NetBoxTable.Meta):
|
|
166
156
|
model = IPFabricSync
|
|
167
|
-
fields = (
|
|
168
|
-
|
|
169
|
-
"id",
|
|
170
|
-
"interval",
|
|
171
|
-
"last_synced",
|
|
172
|
-
"last_ingestion",
|
|
173
|
-
"name",
|
|
174
|
-
"scheduled",
|
|
175
|
-
"status",
|
|
176
|
-
"snapshot_name",
|
|
177
|
-
"user",
|
|
178
|
-
)
|
|
179
|
-
default_columns = ("name", "status", "last_ingestion", "snapshot_name")
|
|
157
|
+
fields = ("id", "status", "snapshot_name")
|
|
158
|
+
default_columns = ("id", "status", "snapshot_name")
|
|
180
159
|
|
|
181
160
|
|
|
182
161
|
class IPFabricIngestionChangesTable(NetBoxTable):
|
|
@@ -262,5 +241,5 @@ class IPFabricDataTable(NetBoxTable):
|
|
|
262
241
|
|
|
263
242
|
class Meta(NetBoxTable.Meta):
|
|
264
243
|
model = IPFabricData
|
|
265
|
-
fields = ("snapshot_data", "JSON")
|
|
266
|
-
default_columns = ("snapshot_data", "JSON")
|
|
244
|
+
fields = ("snapshot_data", "type", "JSON")
|
|
245
|
+
default_columns = ("snapshot_data", "type", "JSON")
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
{% extends 'base/layout.html' %}
|
|
2
|
+
{% load buttons %}
|
|
3
|
+
{% load helpers %}
|
|
4
|
+
{% load perms %}
|
|
5
|
+
|
|
6
|
+
{% block title %}Ingestion{% endblock %}
|
|
7
|
+
|
|
8
|
+
{% block tabs %}
|
|
9
|
+
<ul class="nav nav-tabs px-3">
|
|
10
|
+
<li class="nav-item" role="presentation">
|
|
11
|
+
<a class="nav-link active" role="tab">Ingestion</a>
|
|
12
|
+
</li>
|
|
13
|
+
</ul>
|
|
14
|
+
{% endblock tabs %}
|
|
15
|
+
|
|
16
|
+
{% block controls %}
|
|
17
|
+
<div class="controls">
|
|
18
|
+
<div class="control-group">
|
|
19
|
+
{% block extra_controls %}{% endblock %}
|
|
20
|
+
{% add_button model %}
|
|
21
|
+
</div>
|
|
22
|
+
</div>
|
|
23
|
+
{% endblock controls %}
|
|
24
|
+
|
|
25
|
+
{% block content %}
|
|
26
|
+
<div class="tab-content">
|
|
27
|
+
{% for sync in syncs %}
|
|
28
|
+
<div class="card">
|
|
29
|
+
<h5 class="card-header d-flex justify-content-between" id="module{{ module.pk }}">
|
|
30
|
+
<div>
|
|
31
|
+
<i class="mdi mdi-cloud-sync"></i><i class="mdi mdi-sync"></i> <a href="{% url 'plugins:ipfabric_netbox:ipfabricsync' pk=sync.pk %}">{{ sync.name}}</a>
|
|
32
|
+
</div>
|
|
33
|
+
{% if perms.ipfabric_netbox.delete_ipfabricsync %}
|
|
34
|
+
<a href="{% url 'plugins:ipfabric_netbox:ipfabricsync_delete' pk=sync.pk %}" class="btn btn-danger btn-sm">
|
|
35
|
+
<i class="mdi mdi-trash-can-outline" aria-hidden="true"></i> Delete
|
|
36
|
+
</a>
|
|
37
|
+
{% endif %}
|
|
38
|
+
|
|
39
|
+
</h5>
|
|
40
|
+
<div class="card-body">
|
|
41
|
+
{% include 'inc/sync_warning.html' with object=module %}
|
|
42
|
+
<table class="table table-hover table-headings reports">
|
|
43
|
+
<thead>
|
|
44
|
+
<tr>
|
|
45
|
+
<th width="200">Source</th>
|
|
46
|
+
<th width="400">Snapshot</th>
|
|
47
|
+
<th>Status</th>
|
|
48
|
+
<th>Last Run</th>
|
|
49
|
+
</tr>
|
|
50
|
+
</thead>
|
|
51
|
+
<tbody>
|
|
52
|
+
<tr>
|
|
53
|
+
<td><a href="{% url 'plugins:ipfabric_netbox:ipfabricsource' pk=sync.snapshot_data.source.pk %}">{{ sync.snapshot_data.source.name }}</a></td>
|
|
54
|
+
<td><a href="{% url 'plugins:ipfabric_netbox:ipfabricsnapshot' pk=sync.snapshot_data.pk %}">{{ sync.snapshot_data.name}}</a></td>
|
|
55
|
+
<td>{% badge sync.get_status_display last_job.get_status_color %}</td>
|
|
56
|
+
<td>{{sync.last_synced}}</td>
|
|
57
|
+
</tr>
|
|
58
|
+
</tbody>
|
|
59
|
+
</table>
|
|
60
|
+
</div>
|
|
61
|
+
</div>
|
|
62
|
+
{% empty %}
|
|
63
|
+
<div class="alert alert-info" role="alert">
|
|
64
|
+
<h4 class="alert-heading">Sync Jobs Settings Found</h4>
|
|
65
|
+
{% if perms.extras.add_reportmodule %}
|
|
66
|
+
Get started by <a href="{% url 'plugins:ipfabric_netbox:ipfabricsync_add' %}">creating a sync</a> from an IP Fabric source.
|
|
67
|
+
{% endif %}
|
|
68
|
+
</div>
|
|
69
|
+
{% endfor %}
|
|
70
|
+
</div>
|
|
71
|
+
{% endblock content %}
|
|
@@ -8,7 +8,6 @@ from django.test import TestCase
|
|
|
8
8
|
from django.utils import timezone
|
|
9
9
|
|
|
10
10
|
from ipfabric_netbox.choices import IPFabricSnapshotStatusModelChoices
|
|
11
|
-
from ipfabric_netbox.models import IPFabricIngestion
|
|
12
11
|
from ipfabric_netbox.models import IPFabricSnapshot
|
|
13
12
|
from ipfabric_netbox.models import IPFabricSource
|
|
14
13
|
from ipfabric_netbox.models import IPFabricSync
|
|
@@ -119,9 +118,9 @@ class IPFabricTransformMapModelTestCase(TestCase):
|
|
|
119
118
|
)
|
|
120
119
|
sync = IPFabricSync.objects.create(
|
|
121
120
|
name="ingest",
|
|
121
|
+
type="dcim",
|
|
122
122
|
status="new",
|
|
123
123
|
snapshot_data=snapshot,
|
|
124
|
-
update_custom_fields=True,
|
|
125
124
|
parameters={
|
|
126
125
|
"vrf": False,
|
|
127
126
|
"site": True,
|
|
@@ -139,8 +138,6 @@ class IPFabricTransformMapModelTestCase(TestCase):
|
|
|
139
138
|
},
|
|
140
139
|
)
|
|
141
140
|
|
|
142
|
-
ingestion = IPFabricIngestion.objects.create(sync=sync)
|
|
143
|
-
|
|
144
141
|
runner = IPFabricSyncRunner(
|
|
145
142
|
settings={
|
|
146
143
|
"site": True,
|
|
@@ -155,11 +152,7 @@ class IPFabricTransformMapModelTestCase(TestCase):
|
|
|
155
152
|
"snapshot_id": "12dd8c61-129c-431a-b98b-4c9211571f89",
|
|
156
153
|
},
|
|
157
154
|
sync=sync,
|
|
158
|
-
ingestion=ingestion,
|
|
159
155
|
)
|
|
160
|
-
# Need to monkeypatch since we are not in active Branch (different schema)
|
|
161
|
-
# Using default schema "default" here since we are in "test_netbox" DB
|
|
162
|
-
runner.get_db_connection_name = lambda: "default"
|
|
163
156
|
|
|
164
157
|
site_data = {
|
|
165
158
|
"siteName": "MPLS",
|
|
@@ -173,9 +166,7 @@ class IPFabricTransformMapModelTestCase(TestCase):
|
|
|
173
166
|
"networksCount": 6,
|
|
174
167
|
}
|
|
175
168
|
|
|
176
|
-
self.site = runner.
|
|
177
|
-
item=site_data, app_label="dcim", model="site", cf=sync.update_custom_fields
|
|
178
|
-
)
|
|
169
|
+
self.site = runner.get_model_or_update("dcim", "site", site_data)
|
|
179
170
|
|
|
180
171
|
device_data = {
|
|
181
172
|
"id": "961251111",
|
|
@@ -213,36 +204,14 @@ class IPFabricTransformMapModelTestCase(TestCase):
|
|
|
213
204
|
"slug": None,
|
|
214
205
|
}
|
|
215
206
|
|
|
216
|
-
self.mf_obj = runner.
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
)
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
app_label="dcim",
|
|
225
|
-
model="devicetype",
|
|
226
|
-
cf=sync.update_custom_fields,
|
|
227
|
-
)
|
|
228
|
-
self.platform = runner.sync_item(
|
|
229
|
-
item=device_data,
|
|
230
|
-
app_label="dcim",
|
|
231
|
-
model="platform",
|
|
232
|
-
cf=sync.update_custom_fields,
|
|
233
|
-
)
|
|
234
|
-
self.role = runner.sync_item(
|
|
235
|
-
item=device_data,
|
|
236
|
-
app_label="dcim",
|
|
237
|
-
model="devicerole",
|
|
238
|
-
cf=sync.update_custom_fields,
|
|
239
|
-
)
|
|
240
|
-
self.device_object = runner.sync_item(
|
|
241
|
-
item=device_data,
|
|
242
|
-
app_label="dcim",
|
|
243
|
-
model="device",
|
|
244
|
-
cf=sync.update_custom_fields,
|
|
245
|
-
)
|
|
207
|
+
self.mf_obj = runner.get_model_or_update("dcim", "manufacturer", device_data)
|
|
208
|
+
self.dt_obj = runner.get_model_or_update("dcim", "devicetype", device_data)
|
|
209
|
+
|
|
210
|
+
self.platform = runner.get_model_or_update("dcim", "platform", device_data)
|
|
211
|
+
|
|
212
|
+
self.role = runner.get_model_or_update("dcim", "devicerole", device_data)
|
|
213
|
+
|
|
214
|
+
self.device_object = runner.get_model_or_update("dcim", "device", device_data)
|
|
246
215
|
|
|
247
216
|
def test_transform_map(self):
|
|
248
217
|
site_transform_map = IPFabricTransformMap.objects.get(name="Site Transform Map")
|
|
@@ -414,10 +383,5 @@ class IPFabricTransformMapModelTestCase(TestCase):
|
|
|
414
383
|
)
|
|
415
384
|
transform_field.template = "{{ object.hostname }} - test"
|
|
416
385
|
transform_field.save()
|
|
417
|
-
device_object = runner.
|
|
418
|
-
item=device_data,
|
|
419
|
-
app_label="dcim",
|
|
420
|
-
model="device",
|
|
421
|
-
cf=sync.update_custom_fields,
|
|
422
|
-
)
|
|
386
|
+
device_object = runner.get_model_or_update("dcim", "device", device_data)
|
|
423
387
|
self.assertEqual(device_object.name, "L21PE152 - test")
|